Integer型の変数がNullかどうかチェックするのはなぜ?

クロハです。 前回のPHPの初歩的な部分に躓いた話に引き続いて、たぶん今回もプログラム経験の浅い人向けの内容です。

事の発端

唐突なのですがJavaのソースの改修をしているときに次のような条件分岐に改修する必要な場面に遭遇しました。

// Hogehoge.hogeId はInteger型の変数 private boolean IsHoge(Hogehoge hogeEntity) { Integer hogeId = 0; if(hogeEntity.hogeId != null) { hogeId = hogeEntity.hogeId; } /** 以下割愛 **/ }

内容としてはhogeId に0を代入しておいて引数のhogeEntityクラスのhogeIdがNullじゃなければhogeIdに代入するというものです。他のプログラムの書き方を見ていて思いついた処理ですね。 ちなみにこの割愛している部分の処理でDBに接続してレコードを検索するのですがフレームワークの都合でhogeIdがNullだとwhere条件句からhogeIdの指定条件が消えてしまうのでhogeIdが異なっても他の条件に一致していればレコードがヒットしてしまうという事情があり上記の条件分岐と代入処理が必要でした。 この時「int」と「Integer」の違いを知らなかった私は「Integerってつまりintでしょ?nullにしようにも0しか入らなくない?」と最初は思っていました。
その後、「そもそもintにnull入れようとするとNullPointerExceptionが出るから0にすらならない..?」とか色々自分の中でも矛盾が発生したので気になって調べた、というのが今回の事の発端です。

「int」と「Integer」の違いとは

ざっくりと書くと
  • int → プリミティブ型
  • Integer → 参照型(intのラッパークラス)
ここで上記の条件の理由が分かった人はプログラミングを基礎からちゃんと理解できてる人だと思います。(というか「int null なぜ」とかググったりしないですよね..) 話を戻します。 まずラッパークラスについてですがこれも言い換えれば「基本のデータ型やオブジェクトを使いやすくするためにメソッドなどを追加したクラス」です。 あまりいい例が思いつかなかったのですが洗濯機を例にします。 洗濯の工程として 対象を投入する→洗う→干す(乾燥する)→畳む を想定すると 普通の洗濯機(基本のデータ型)では投入された対象を洗う機能しかないとなると乾燥以降の手順を自前で行う必要があります。 しかし乾燥機能付きの洗濯機(洗濯機のラッパークラスとする)であれば洗う→乾燥までの工程を洗濯機側でやってくれるので人間は対象の投入と畳む工程をやればよい、という感じですね。文明の利器バンザイ。 次にプリミティブ型と参照型についてですが、 プリミティブ型→値を持つ 参照型→メモリ上の値が格納されているアドレスを持つ という違いがあります。 こちらの記事 に良い具体例があったので引用させていただくと
int a = 1;
int b = a; // bにはaの値:1が格納される
a = 2;

Systemout.println(a); // 2 が出力される
Systemout.println(b); // 1 が出力される

int[] a = {1, 2, 3};
int[] b = a;
a[0] = 0;

Systemout.println(a[0]); // 0 が出力される
Systemout.println(b[0]); // 0 が出力される
int[] b = a の箇所ではint配列aと同じアドレスを見ているためaの値の変更が反映されていますね。 雑な結論ではありますが値が格納されているアドレスを見ているので「値の入っている場所(アドレス)が指定されていない」という状態が参照型におけるNullなのだと個人的に解釈しました。

終わりに

さて、そろそろ話を締めます。 今回学んだことを踏まえて発端となった条件分岐を考えると 引数のHogehogeエンティティのhogeIdがNullの場合は Hogehoge.hogeId に入る値のアドレスがない=アドレスが指定されていない=Null となることがあり得るということでした。 自分の備忘録的な部分がメインですが同じような疑問を持っていた方の助けになればと思います。

PHP触ったことのないプログラマーが動的な型付けに頭を抱えた話


こんにちは!5月に入社した黒羽(くろは)です!


隔週での更新を目標に開発一同で再開しましたテックブログ、再開後としては2つ目(?)の記事になります。
いきなり社歴の浅い新人が書いているので驚かれる方もいると信じて簡単な自己紹介をしつつ、本題に入っていこうと思います。 
さて、文系の学部を出てプログラマーになって6年目も中盤に差し掛かっておりますが、大学在学中よりも時間の経過が速かった気がします。
今までに取り扱った言語としては以下のような感じです。
  • Java
  • C#
  • Python
  • javascript
  • CSS
  • HTML
  • その他にコマンドプロンプト(世の人々がプログラムと聞いてイメージするであろう『黒い画面に文字がダーッとでる』やつ)で使用するスクリプト類など…
  最後のは言語じゃなくてコマンドの塊じゃん!と言われそう イメージマジックの開発で主に使われている言語はPHPなので入社して初めてPHPを触りました。
今まで触ってきた言語とは書き味や仕様が違っていて日々調べながら習得を進めています。
今日はそんな言語仕様に振り回されてハマってしまった時の原因について調べたので(社内ではそりゃ当たり前だろといわれそうなので未来のPHP初学者に向けて)共有できればと思います。

きっかけ


イメージマジックの各プロジェクトのPHPのソースコードを眺めているとどのソースにも
declare(strict_types=1);
と出てきます。 これは何ぞやと調べてみたところ次の記事に行き当たりました。
この記事の冒頭でこのように記述されていました。
 
 
declare(strict_types=1); とは、PHP7から導入された、厳格な型検査モードの指定構文です。

厳格な型検査モード・・・・?????と思いつつ読み進めてみると更に以下の記述が。
<?php
function add(int $a, int $b): int
{
    return $a + $b;
}

var_dump(add(1.0, 2.0));
この状態で単体実行すると、int(3)が出力されます。
  この時の私の頭の中では1つの考えに支配されていました。
「int型の変数にdouble型を堂々と代入するな!!!!!!」


言語による型付け


あまりに理解ができなくて大声になりましたが気を取り直していきます。
プログラミング言語には開発者が変数や戻り値になど事前にどういう型のデータが入るかを指定する静的な型付けと実行時にコンパイラやインタプリンタがコードを解釈してよしなにデータ型を判断してくれる動的型付けがあります。
PHPは言語仕様として動的な型付けを行っています。
私はJava()静的な型付け)からプログラミングをはじめて、当時の学習用テキストにもデータ型が一致しないとエラー吐くと脅されて教えられました。
そのため、データ型が定義されている変数に異なるデータ型を突っ込まれると困ってしまうわけです。
しかもdouble同士の加算でint型で返ってくるとはこれ如何に。
上記のサンプル関数では$a=1.0≒1(int型)、$b=2.0≒2(int型)としてPHPの方で判断しているようです。
話は逸れましたがdeclare(strict_types=1);を付けてさきほどの関数を実行すると

PHP Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, float given, called in /Users/hiraku/sandbox/stricttypes/A.php on line 9 and defined in /Users/hiraku/sandbox/stricttypes/A.php:4 Stack trace: #0 /Users/hiraku/sandbox/stricttypes/A.php(9): add(1, 2) #1 {main} thrown in /Users/hiraku/sandbox/stricttypes/A.php on line 4

とエラーが発生します。
つまりdeclare(strict_types=1);とは動的な型付けをしているPHPで静的な型付けのように振舞わせる宣言という風に解釈できます。
ちなみにこの宣言をしているファイルを呼び出している場合のみ有効であるため、別ファイルからuse文等で参照して同じように関数を呼び出しても緩い型検査のままであるのでint型で返ってきます。
つまり引数を$a=1.0,$b=2.0とする場合、
  • declare(strict_types=1); を宣言しているファイルAのadd関数を呼び出す⇒Typeエラーが発生
  • declare(strict_types=1); を宣言しているファイルAを参照しているがdeclare(strict_types=1); を宣言していないファイルBでファイルAのadd関数を呼び出す⇒int型の戻り値が返却される。 
ということです。

最後に


つまるところ何が言いたかったのかというとカルチャーショックを受けたという話でした。
動的な型付けは手軽な一方で想定していない値が返ってくる可能性があるのでやはり多少面倒でも静的な型付けできっちりとデータ型を指定して書く方が私は好きです。(というかぱっと見で何の値が入ってくるのかソースから読み取れないと後々困ることもある)
最後に余談となりますがこの記事を書いてる間に裏どりがてらに色々調べていたのですが 「動的な型付け」と「型推論」は同じものだと思っていましたが違うもののようです。
ここら辺も少し調べてみようと思います。
ここまで読んでいただきありがとうございました。何かの参考になれば幸いです!