Javaで2つの引数をチェックします。どちらもnullでないか、どちらもエレガントにnullです


161

私は春のブートを使用して、メールの送信に使用するシェルプロジェクトを開発しました

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

fromおよびpassword引数がない場合は、デフォルトの送信者とパスワードを使用します(例:noreply@bar.comと)123456

したがって、ユーザーがfrom引数を渡す場合、password引数も渡す必要があり、その逆も同様です。つまり、両方がnullでないか、両方がnullです。

これをエレガントにチェックするにはどうすればよいですか?

今私の方法は

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

14
余談ですが、空白を注意深く使用するとコードが非常に読みやすくなることに注意してください。現在のコードで演算子間にスペースを追加するだけで、読みやすさIMOが大幅に向上します。
Jon Skeet、2016年

8
「優雅さ」を定義してください。
Renaud

SMTP認証資格情報とエンベロープ送信者の電子メールアドレスには、個別の引数セットが必要です。From電子メールアドレスは、常にSMTP認証名ではありません。
Kaz

3
それは本当にもっと最適化することはできません、それは一行の読みやすいコードであり、それを過度に最適化しても何も得ることができません。
diynevala

3
補足:これがシェルスクリプトの場合、パスワードはシェルの履歴に保存されませんか?
体に触れる

回答:


331

^XOR)演算子を使用する方法があります:

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

if1つの変数のみがnullの場合は条件が真となります。

しかし、通常は、if例外メッセージが異なる2つの条件を使用する方が良いと思います。単一の条件を使用して問題の原因を定義することはできません。

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
2つのブールのxorが2つのブールよりも望ましい理由はあり!=ますか?
Eric Lippert、2016年

1
うわー、ブール値のXORがと同じであることを知りませんでした!=。マインドブローン。そして、それはコメントで非常に多くの賛成票です。そして、このコメントに品質を追加するには、はい、私はまた、ユーザーがエラーを修正する方法をよりよく知っているので、異なるエラーケースに対して異なるエラーメッセージを与えることはより良いと思います。
16年

1
2つの要素で問題ありません。> 2要素でこれを行うには?
Anushree Acharjee 2017

要素のいずれかが0より大きい場合、エラーメッセージを表示します。デフォルトでは、すべてが0に設定されています。すべてが0より大きい場合は、有効なシナリオです。これを行う方法?
Anushree Acharjee 2017

これは気の利いたインタビューの質問になります。プログラマの何%がそれを知っているのだろう。しかし、それが十分に明確ifではなく、2つの句に分割する必要があることには同意しません。例外のメッセージはそれを適切に文書化します(質問を編集しましたが、受け入れられるまで表示されません)
Adam

286

さて、2つの「無効」条件が同じであるかどうかを確認しようとしているようです。あなたは使うことができます:

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

または、ヘルパー変数を使用してより明示的にします。

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

18
あなたは私のSO heros @Kazの1人ですが、「これかそれともどちらかではありませんが、両方が同じではない」と^は言っていません。:-)
デビッドブロック

6
@DavidBullock:ブール値に関しては、「どちらかと言えばどちらでもない」のように...「両方とも同じではない」とは言えません。XOR 、ブール値に対する「等しくない」関数の別名です。
Kaz

5
@Kaz ^「ねえ、私のオペランドはブール値です」という意味で!=はありません。(残念ながら、「このオペランドは数値である可能性があり、この時点では関係演算子ではない可能性がある」をチェックする必要があるため、この影響は軽減されますが、不利な点です)。私に反対しているにもかかわらず、あなたのヒーローの地位は衰えていません:-)
デビッドブロック

3
問題の要件を読んだ後、あなたの解決策であるコードは、理にかなっていて読みやすいです。しかし、4年間の開発者として(まだ在学中)、このコードを読んでから、「ビジネス要件」がどこにあるのかを理解しようとすると頭痛の種になります。このコードから、私はすぐに理解していない「frompasswordヌルであるか、またはその両方ではないnullでなければなりません両方」。多分それは私と私の経験不足ですが、@ stig-hemmerの回答のように、さらに3行のコードが必要な場合でも、より読みやすいソリューションを好みます。私はすぐには理解しないと思います bool != bool -直感的ではありません。
Chris Cirefice

2
@DavidBullock ^演算子はビット単位の演算子です。実際には、そのオペランドがブール値であることを意味するものではありません。ブールxor演算子はxorです。
ブリリアント、

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

6
@mattあなたの批判はコードではなく、例外のテキストにあるように見えます。しかし、この回答のメリットは、エラーメッセージの内容ではなく、ifステートメントの構造にあると思います。機能が向上するため、これが最良の答えです。OPは、例外テキストを好きな文字列に簡単に置き換えることができます。
Dan Henderson

4
@matt利点は、2つの無効な状態のどちらが発生したかを区別し、エラーメッセージをカスタマイズできるようになることです。したがって、「これらの2つのことの1つを間違って実行した」と言う代わりに、「これを実行した」または「それを実行した」と言うことができます(その後、必要に応じて解決手順を提供します)。どちらの場合も解像度は同じになる可能性がありますが、「これらのいずれかを誤って実行した」と言ってはいけません。出典:この機能を提供できない環境で、エラーテキストの最初の条件を満たすユーザーから多くの質問に回答しました。
Dan Henderson

4
@DanHenderson>「あなたがこれらのことの1つを間違った」と言うのは決して良い考えではありません。同意しません。パスワード/ユーザー名ユーザー名とパスワードが一致しないと言った方が安全です。
マット

2
@DanHendersonセキュリティの観点からは、ユーザー名がディレクトリ内にある場合とそうでない場合を区別しない方がよい場合があります。そうでない場合、攻撃者は有効なユーザー名を見つけることができます。ただし、nullとnot nullの混合は(この場合)常に使用エラーであり、より詳細なエラーメッセージを表示しても、最初にユーザーが提供した情報よりも多くの情報が漏洩することはありません。
siegi

16

その機能をシグネチャを使用して2つの引数を持つメソッドに配置します。

void assertBothNullOrBothNotNull(Object a, Object b) throws RuntimeException

これにより、関心のある実際のメソッドのスペースが節約され、読みやすくなります。少し冗長なメソッド名でも問題はなく、非常に短いメソッドでも問題はありません。


14
スペースの節約はありませんが((from == null) != (password == null))、それも非常に簡単に理解できます。役に立たないメソッドに問題があります。
edc65

3
ifステートメントには1行、throwステートメントには2行目、右中括弧には3行目があります。すべて1行に置き換えられます。中かっこに新しい行を追加すると、さらに1行節約されます。
Traubenfuchs 2016年

10
コードを読むとき、1つのメソッド名を持っている必要があります。
Traubenfuchs 2016年

1
間違いなく+1。常に、わかりやすい実装の詳細と演算子を抽象化して、INTENTIONを明確にする説明的なメソッドと変数名で抽象化することを優先します。ロジックにはバグが含まれています。コメントはさらに悪い。メソッド名は意図を示し、ロジックを分離するので、エラーを見つけて修正するのが簡単です。このソリューションでは、読者が構文やロジックを知ったり、考えたりする必要はまったくありません。代わりに、ビジネス要件に焦点を当てることができます(これは本当に重要なことです)
sara

1
ここにいくつかの説明的な例外メッセージが必要な場合は、不要な問題が発生します。
Honza Brabec 2016年

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)不必要に2つのフレームをプッシュおよびポップします。Objects.isNull(Object)があるのは、「このメソッドは述語として使用するために存在する」ためです(ストリーム内など)。
デビッドブロック2016年

5
このような単純な方法は通常、JITによってすぐにインライン化されるため、パフォーマンスへの影響はほとんどありません。実際に、使用方法について議論するObjects.isNull()こともできます。必要に応じて独自に作成することもできますが、読みやすさに関しては、使用するisNull()方が良いと思います。さらに、単純な式をコンパイルするには、追加の括弧が必要ですfrom == null != (password == null)
Didier L

2
JITの方法(および操​​作の順序...怠惰)に同意します。それでも、nullと比較するために提供されている機能は非常に多い(val == null)ため、メソッドが非常に機能的でインライン化可能であり、十分に機能しているとしても、2つの大きなファットメソッド呼び出しを直視するのは難しい名前付き。それは私だけです。私は最近、私は穏やかに変だと決めました。
David Bullock

4
正直なところ、誰が単一のスタックフレームを気にしますか?スタックが問題になるのは、(無限の可能性がある)再帰を処理している場合、またはサハラ砂漠のサイズのオブジェクトグラフを持つ1000層のアプリケーションがある場合だけです。
sara

9

これは、任意の数のnullチェックの一般的な解決策です

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

もう1つの利点は、デフォルトのいずれかがnullの場合でも検出されることです。


そうは言っても、一般的には、XORの使用は、それが必要な演算子である場合は許容できると思います。それ言語の一部であり、難解なコンパイラバグのために機能するトリックだけではありません。
私はかつて、三項演算子を使用するには混乱しすぎていることに気付いた牛職人がいました...


1
私はあなたの答えの1つをサンプリングするためにランダムに選んだので、約束のxkcdリンクを見つけることができないので、反対票を投じました!恥を知れ!
dhein 2016年

@ザイビス私の守備では、それは単なる趣味であり、フルタイムの仕事ではありません。しかし、私はそれを見つけることができるかどうかを確認します...
SQB 2016年

8

私が実際にこのコードを書く方法である別の代替案を提案したいと思います:

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

私にとって、これはこの状態を表現する最も自然な方法であるように思われ、将来それを読む必要があるかもしれない誰かにとって理解しやすくします。また、より有用な診断メッセージを提供し、不要なパスワードをそれほど深刻ではないものとして扱うことができ、そのような状態になりそうな変更を簡単に行うことができます。つまり、コマンドライン引数としてパスワードを指定するのは最善の方法ではなく、引数がない場合はオプションで標準入力からパスワードを読み取れるようにすることもできます。または、余計なパスワード引数を黙って無視することもできます。このような変更では、全体を書き直す必要はありません。

その上、最小数の比較のみを実行するので、より「優雅な」代替よりも高価ではありません。新しいプロセスを開始することは、余分なnullチェックよりもはるかに高価であるため、パフォーマンスが問題になることはほとんどありません。


私は多少会話的にコメントするので、「//ヌルフロム」で他のことを続けるかもしれません。例の簡潔さはこれを本当にマイナーにします。私はロジックの明確さと、これが提示するテストの削減が好きです。
ネイト

これは完全にうまく機能しますが、ステートメントが明確ではなく、雑然としているように見える場合はネストします。私はこの答えは別のオプションとして、ここでうれしいので、かかわらず、単に個人的な意見であること
ケビン・ウェルズ

エレガンスに関する限り、これはおそらくこれを行う最もエレガントな方法の1つです。
dramzy

7

これを処理する正しい方法は、3つの状況を検討することだと思います。「from」と「password」の両方が提供され、どちらも提供されず、2つの組み合わせが提供されます。

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

2
このコードは、機能しないためではなく、理解するのが非常に難しいため、間違っています。他の人が書いたその種のコードのデバッグは、悪夢に過ぎません。
NO_NAME 2016年

2
@NO_NAMEなぜ理解が難しいのかわかりません。OPは3つのケースを提供しました。フォームとパスワードの両方が提供された場合、どちらも提供されなかった場合、および例外をスローする必要がある混合ケースです。中間条件を参照して、両方がnullかどうかを確認していますか?
マット

2
@NO_NAMEに同意します。その中間のケースは解析に時間がかかりすぎて一見できません。上の行を解析しない限り、nullが中央に関係することはありません。その場合のfromとpasswordの同等性は、実際にはnullでないことの副次的効果です。
jimm101 2016年

1
@ jimm101はい、それが問題であることがわかりました。パフォーマンス上の利点は、より詳細なソリューションでは最小限/検出不可能になります。それが問題であるかどうかはわかりません。更新します。
マット

2
@mattパフォーマンス上の利点はないかもしれません-コンパイラが何をするか、そしてこの場合チップがメモリから何を引き出すかは明確ではありません。プログラマサイクルの多くは、実際には何も得られない最適化に焼き付けられる可能性があります。
jimm101 2016年

6

Xor ogの長いifを含まない比較的簡単な方法を次に示します。ただし、少し冗長にする必要がありますが、利点として、私が提案したカスタムの例外を使用して、より意味のあるエラーメッセージを取得できます。

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ダッタ

1
本当に例外的なものと、日常的に発生し、通常の実行の一部と見なすことができるものを混同していると思います。Java docは例外的な例を使用しています。つまり、ファイルの読み取り中にメモリが不足しています。無効なパラメータの発生は例外ではありません。「通常の制御フローには例外を使用しないでください。」- blogs.msdn.com/b/kcwalina/archive/2005/03/16/396787.aspx
プー

さて、最初に:欠落した/適切な引数が例外でない場合、なぜIllegalArgumentExceptionが存在するのですか?第2に、無効な引数は例外ではないと本当に確信している場合は、それも検証すべきではありません。言い換えれば、アクションを実行して、何かがうまくいかない場合に例外をスローするだけです。このアプローチは問題ありませんが、あなたが話しているパフォーマンスのペナルティは、私が提案したアプローチではなく、ここで発生します。Java例外はスローされても遅くなりません。回復に時間がかかるのはスタックトレースです。
Arnab Datta 2016

コンテキストが重要です。OPのコンテキスト(sendmailアプリ)では、ユーザー名とパスワードのいずれかを忘れることが一般的で予期されています。ミサイルガイダンスアルゴでは、null座標パラメーターは例外をスローする必要があります。パラメータを検証しないとは言いませんでした。OPのコンテキストでは、アプリケーションロジックを駆動するために例外を使用しないでください。スタックトレースの巻き戻しは、私が話しているパフォーマンスのヒットです。あなたのアプローチを使用すると、最終的にはPasswordMissingException / FromEmailMissingExceptionをキャッチする(つまり、巻き戻す)か、またはさらに悪いことに、処理されないようにする。。
2016年

6

三項演算子については誰も言及していないようです。

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

この特定の条件をチェックするにはうまくいきますが、2つの変数をあまり一般化していません。


1
ルックスの素敵な、しかし、困難を理解することよりも^!=2 boolsまたは2とifS
coolguy

@coolguy私の推測では、三項演算子はXOR演算子よりもはるかに一般的であり(ビット演算を実行していないコードでXORを見るたびに「メンタルサプライズ」は言うまでもありません)、この定式化はカットを回避する傾向がありますifを悩ますエラーを貼り付けます。
gbronner

推奨されません。これは(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

5

私があなたの意図を理解しているように、常に両方の排他的無効性をチェックする必要はありませんがpasswordfromがあります。がnullのpassword場合、指定された引数を無視して独自のデフォルトを使用できますfrom

疑似で書かれたものは次のようでなければなりません:

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た場合、パスワードを上書きすることを意図していた可能性がありますが、デフォルトのアカウントは保持します。ユーザーがそれを行った場合、それは無効な指示であり、そのように指示する必要があります。入力が有効であるかのようにプログラムが続行することはありません。先に進み、ユーザーが指定していないパスワードでデフォルトのアカウントを使用してみてください。私はそのようなプログラムで誓います。
デビッドブロック2016年

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以外の値を持つことができます。

このアプローチの利点の1つは、アカウントを使用するコードが実行されたときではなく、アカウントが最初に取得されたときに、一方のフィールドをnullにしないというエラーが発生することです。アカウントを使用するコードが実行されるまでに、データが無効になることはありません。

この方法のもう1つの利点は、セマンティックな情報が提供されるため、読みやすくなります。また、他の場所で名前とパスワードを一緒に必要とする可能性があるため、追加のクラスを定義するコストは、複数の使用に渡って償却されます。


new Account(null, null)nullの名前とパスワードの両方を持つアカウントがまだ有効であっても、NPEがスローされるため、期待どおりに機能しません。
HieuHT

名前とパスワードがnullの場合、nullを渡すのは、現実の世界でアカウントが存在するかどうかに関係なく、という考え方です。
モニカを

OPから得られるものは、 つまり、両方がnullでないか、両方がnullであるということです。名前とパスワードの両方がnullのAccountオブジェクトを作成するにはどうすればよいですか?
HieuHT

@HieuHT申し訳ありませんが、私の最後のコメントは不明瞭でした。名前とパスワードがnullの場合は、のnull代わりに使用しますAccount。あなたは合格しないだろうnullAccount、コンストラクタ。
モニカを
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.