Check two arguments in Java, either both not null or both null elegantly

Java

Java Problem Overview


I used spring boot to develop a shell project used to send email, e.g.

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

If the from and password arguments are missing, I use a default sender and password, e.g. [email protected] and 123456.

So if the user passes the from argument they must also pass the password argument and vice versa. That is to say, either both are non-null, or both are null.

How do I check this elegantly?

Now my way is

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

Java Solutions


Solution 1 - Java

There is a way using the ^ (XOR) operator:

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

The if condition will be true if only one variable is null.

But I think usually it's better to use two if conditions with different exception messages. You can't define what went wrong using a single condition.

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

It is more readable and is much easier to understand, and it only takes a little extra typing.

Solution 2 - Java

Well, it sounds like you're trying to check whether the "nullity" condition of the two is the same or not. You could use:

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

Or make it more explicit with helper variables:

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

Solution 3 - Java

Personally, I prefer readable to elegant.

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

Solution 4 - Java

Put that functionality in a 2 argument method with the signature:

void assertBothNullOrBothNotNull(Object a, Object b) throws RuntimeException

This saves space in the actual method you are interested in and makes it more readable. There is nothing wrong with slightly verbose method names and there is nothing wrong with very short methods.

Solution 5 - Java

A Java 8 solution would be to use Objects.isNull(Object), assuming a static import:

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

For Java < 8 (or if you don't like using Objects.isNull()), you can easily write your own isNull() method.

Solution 6 - Java

Here is a general solution for any number of null checks

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

Uses

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

Solution 7 - Java

Since you want to do something special (use defaults) when both sender and password are absent, handle that first.
After that, you should have both a sender and a password to send an e-mail; throw an exception if either is missing.

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

An added benefit is that, should either of your defaults be null, that will be detected as well.


Having said that, in general I think that use of XOR should be permissible when that is the operator you need. It is a part of the language, not just some trick that works because of an arcane compiler-bug.
I once had a cow-orker who found the ternary operator too confusing to use...

Solution 8 - Java

I would like to suggest another alternative which is how I would actually write this piece of code:

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

To me this seems to be the most natural way to express this condition which makes it easy to understand for somebody who might have to read it in the future. It also allows you to give more helpful diagnostic messages and treat the unnecessary password as less serious and it makes it easy to modify which is rather likely for such a condition. I.e. you may find out that giving a password as a command line argument is not the best idea and may want allow reading the password from standard input optionally if the argument is missing. Or you may want to silently ignore the superfluous password argument. Changes like these would not require you to rewrite the whole thing.

Besides that it executes only the minimum number of comparisons, so it's not more expensive than the more "elegant" alternatives. Although performance is very unlikely a problem here because starting a new process is already much more expensive than a extra null check.

Solution 9 - Java

I think a correct way to handle this is to consider three situations: both 'from' and 'password' are provided, neither are provided, a mix of the two are provided.

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

It sounds like the original question wants to break the flow if it is incorrect input, but then they will have to repeat some of the logic later. Perhaps a good throw statement might be:

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

Solution 10 - Java

Here's a relatively straight-forward way that does not involve any Xor og lengthy ifs. It does however require you to be slightly more verbose, but on the upside, you can use the custom Exceptions I suggested to get a more meaningful error message.

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

Solution 11 - Java

Nobody seems to have mentioned the ternary operator:

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

Works nicely for checking this particular condition, but doesn't generalize well past two variables.

Solution 12 - Java

As I see your intentions, there is no need to always check both exclusive nullities but to check if password is null if and only if from is not null. You can ignore the given password argument and use your own default if from is null.

Written in pseudo must be like this:

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
}

Solution 13 - Java

I'm surprised nobody mentioned the simple solution of making from and password fields of a class and passing a reference to an instance of that class:

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

Here from could be null or non-null and if it's non-null, both its fields have non-null values.

One advantage of this approach is that the error of making one field but not the other field null gets triggered where the account is initially obtained, not when the code using the account runs. By the time the code using the account is executed, it's impossible for the data to be invalid.

Another advantage to this approach is more readable as it provides more semantic information. Also, it's likely that you require the name and password together in other places so the cost of defining an additional class amortizes over multiple usages.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionzhuguoweiView Question on Stackoverflow
Solution 1 - JavacoolguyView Answer on Stackoverflow
Solution 2 - JavaJon SkeetView Answer on Stackoverflow
Solution 3 - JavaStig HemmerView Answer on Stackoverflow
Solution 4 - JavaASAView Answer on Stackoverflow
Solution 5 - JavaDidier LView Answer on Stackoverflow
Solution 6 - JavaKhaled.KView Answer on Stackoverflow
Solution 7 - JavaSQBView Answer on Stackoverflow
Solution 8 - Javax4uView Answer on Stackoverflow
Solution 9 - JavamattView Answer on Stackoverflow
Solution 10 - JavaArnab DattaView Answer on Stackoverflow
Solution 11 - JavagbronnerView Answer on Stackoverflow
Solution 12 - JavaJordiVilaplanaView Answer on Stackoverflow
Solution 13 - JavaReinstate MonicaView Answer on Stackoverflow