JavaScriptで文字列の等価性を==で評価できるのは文字列が基本型だからなのか
…というような疑問点が以下のエントリを読んで浮かびました。
表題のことを考えるついでに、私も普通の業務系(Java)プログラマが知っておくべきことを絡めてこのエントリを書いていきたいと思います。
まず、JavaプログラマがWebブラウザ上でリッチクライアントを実現する場合、JavaScriptを生で触る以外にも選択肢があります。GWT(Google Web Toolkit)は、JavaプログラムをJavaScriptへ変換してくれるツールキットです。これを用いることで
- JUnitなど、今までJava開発で用いてきたライブラリや、それらのライブラリを使用してきた経験を生かすことができます
- (プログラマが記述するのはJavaコードなので)JavaScriptにはない、タイプセーフな環境の上で開発が行えます
GWTの登場でJavaプログラマがJavaScriptを利用したアプリケーションを作り上げることはより容易になりましたが、実際にJavaScriptを導入する、すなわちWebブラウザをクライアントとしてシステムを構築する場合にはプログラミングスキルだけではなく、その他色々な検討が必要になると思います。
- (普段Webを利用しているとあまり気づかなかったり問題の無いレベルのように感じるかもしれませんが)業務アプリは普段目にしているようなWebアプリより局所的には高い性能が求められることがあります。例えば一画面中に表示する必要がある情報量など。何となくWebアプリはJavaアプリより軽量だと感じているかもしれませんが、それはあなたの利用しているWebサービスがそういうふうに工夫しているからであって、単純にあなたが作成している業務アプリをWebアプリへ移植すると、ブラウザのメモリ使用量が大変なことになるでしょう。そしてあなたのアプリに求められている要件に対しては工夫だけでは如何ともしがたい可能性があります。
- Ext GWT(GXT; Ext JSのGWT移植版です)を利用することなどにより、リッチなGUIを実現できるようにはなりました。が、やはりネイティブアプリに比べると実現できることには限りがあります。そしてGUIを凝ったものにすればするほど重くなります。
- JavaはJVM上で動作しますが、同様に、JavaScriptはWebブラウザ上で動作します。そしてWebブラウザの壁はJVMの壁より厚いです。OSネイティブな機能を用いれば実現できることがわかっていたとしても遠回りをしなければならなかったり、そもそも実現が不可能だったりします
- ActiveXコントロールの悲劇を繰り返すことは現在でも可能かもしれませんが…
もしかすると、再度Appletに目を向けてみる良い機会かもしれません。
- Java6 update10より(ちなみに現時点の最新版はupdate25です)次世代Java Plug-inが搭載され、性能が向上しました。
- JavaFXを軸にAppletの機能向上は続いており(サンプル)、その成果の恩恵はJavaでも享受できるようになるでしょう。
本題に入ります。
表題にさらっと「==で等価性を評価」と記載していますが、ここですでに普通の業務系Javaプログラマが知っておくべき点が隠されています。
- == は言語によって定義が異なります。特に、軽量プログラミング言語に分類される言語は、一般的にJavaの定義とは異なります。
- Javaでは(==をオブジェクトに対して用いるとき)”同一性を評価するのに対し、他の言語では”等価性(論理的等価性、等値性とも)”を評価するのに用いられる場合があります。
同一性、等価性については普通の業務系Javaプログラマなら持っているであろう書籍Effective Java第2版の項目8「equalsをオーバーライドするときは一般契約に従う」、あるいはJavaDocのequalsメソッドの説明を読んでみると理解できると思いますが、要するに同一性の評価とは前述の通りJavaオブジェクトに対する==、等価性の評価とは(適切に実装された)equalsメソッドです。
冒頭で示したエントリでは、
Javaプログラマー的な発想では、strとstr2は別々の文字列オブジェクトを参照しているため、最後の行の==による比較がfalseとなるように勘違いしそうですが、JavaScriptでは基本型として値の比較となるためtrueとなります。
とあり、Javaは文字列がオブジェクトだから==では正しく評価できず、JavaScriptは基本型だから正しく評価できる、というように読めますが、可能性としてはこれ以外にも『==の定義がJavaとJavaScriptで異なる』ということが考えられます。事実、Javaと同じくJVM上で動作するGroovy言語において、文字列はオブジェクトであるにも関わらず、以下の評価はJavaScriptと同様trueとなります。
1: str = "Hello";
2: str2 = "Hello World".substring(0, 5);
3:
4: println (str == str2); // true
これはGroovyが==を(Javaとは異なり)等価性の評価として使用しているからです。以下にGroovyサイトの”Japanese Differences from Java”良くある落とし穴、の節を引用します。
すべての型において==はequalsを意味します。Javaには、==が基本型については同値性を意味し、オブジェクトにとっては同一性を意味する、という構文上奇妙な部分があります。Groovyではautoboxingを導入しているため、このことはJava開発者の大混乱を招く恐れがあります (なにせxが5のとき、x == 5はほとんどfalseになるでしょうから:)。そこで、単純化のためにGroovyでは==はequals()を意味することになっています。もし本当に同一性が必要なときは、foo.is(bar) というように "is" メソッドを使うことができます。これはnullには使えませんが、その場合には==を使って foo==null とすることができます。
この引用の中で、”同値性”という言葉が出てきます。同値というのは、書籍JavaScript 第5版から引用すると「通常、バイト単位で比較して等しい」(p.42)という意味です。
等価性、同値性、同一性の関係をベン図示しておきます(クリックで拡大)。
つまり、同一であれば同値であるし、同値であれば等価でもあります。ただし逆は成り立ちません。余談になりますが、文脈によっては「等価である」というのは、言外に「同値ではないけれども」と含んでいる場合もありますので注意が必要です(同値と同一の関係の場合も同様)。
それでは、JavaScriptではどのような定義になっているのでしょうか。ECMAScript(3rd Ed.)の仕様を日本語訳されている方がいらっしゃったのでそちらのサイトを引用させて頂きます。
文字列に対して==を使うと
11. Type(x) が String ならば、x と y の文字シーケンスが完全に同じ(同じ長さで対応する位置に同じ文字がある)なら true を返し、そうでなければ false を返す。
という定義で評価される、という仕様になっています。つまり、Stringは同値性によって評価されます。この定義を見ればわかる通り、文字列が基本型だから、ではなく、文字列に対して==が同値性評価として機能するから、というのが正しいでしょう。この説明に「等価性」という言葉をねじ込むと、『JavaScriptの==は(同一性ではなく)等価性を評価する演算子である。また、文字列に対して等価性は同値性を以て判断される』ということになるでしょうか。
結果だけを見ると、==を用いるとき、Javaの基本型では同値性評価、JavaScriptの文字列に対しても同値性評価、と同じように見えます。しかし、だからといって『JavaScriptの==はJavaと同じく基本型に対しては同値性の評価を行う』と覚えてしまうと、 true == 1 がtrueになるのかfalseになるのか、あるいはそれ以外なのか判断を誤ります。『同じ値ではないからfalseなのかな?JavaではコンパイルエラーになるけどJavaScriptはコンパイラ言語ではないから実行時エラー?』先に示したECMAScriptの仕様を読めば正解は一目瞭然です。
従って、
- 同じ記号だからといって異なる言語間で意味が同じだと思い込んで使用するのは、特に==演算子においては危険
- (そもそもVBのように==演算子が無い言語や、C++のように演算子をオーバロードできる言語もあります)
ということを念頭に置いておくべきでしょう。もちろん言語設計者は既存の言語を参考にしてはいるでしょうが、それは言語間に差異が無いということを意味しているわけではありません。==ではその差異が顕著に表れています。
あと、長々と記述してきましたが、書籍JavaScript: Good Partsでは、JavaScriptの悪いパーツの筆頭に==が挙げられており邪悪な演算子呼ばわりされてますので、定義を暗記するより先に
- ==演算子は極力使わない
ということを覚えておくのが賢明かもしれません。
最後に。冒頭で示したエントリの、JavaScriptには整数と浮動小数の区別が無い、という例示コード、
1: var intValue = 1;
2: var doubleValue = 1.0;
3: // (中略)
4: // 整数と浮動小数は区別がない
5: console.log(intValue == doubleValue); // true
これをJavaで同じように書くと
1: int i = 1;
2: double d = 1d;
3: System.out.println(i == d);
となります。Javaには整数と浮動小数の区別があるわけですが、実行結果がどうなるか、そしてその理由は何なのかは普通の業務系Javaプログラマならわかりますよね?
[補足]
本文中の「基本型」という用語は参照元エントリに合わせて”primitive datatype”の意味で使用しています。これは、書籍JavaScript 第5版では「基本データ型」と訳されています。一方、本書では「基本型(primitive type)」という用語も登場しますが、基本データ型とは異なる概念です(「基本データ型」と対になるのは「オブジェクト(複合データ型)」、「基本型」と対になるのは「参照型」)。
…と思っていたのですが、参照元エントリをちゃんと読み返すと「基本型」を両方の意味で用いていますね…
« [読書]プログラマが知るべき97のこと その1 『19:誰にとっての「利便性」か』 | トップページ | NetBeans7上でJDK7を用いてEclipseと結果が異なっていたコンパイラの挙動を確認する »
この記事へのコメントは終了しました。
« [読書]プログラマが知るべき97のこと その1 『19:誰にとっての「利便性」か』 | トップページ | NetBeans7上でJDK7を用いてEclipseと結果が異なっていたコンパイラの挙動を確認する »
コメント