普通の業務系PGには意外と知られていないJavaとJavaScriptの相違点10選
以前はJava EEの普通のWebアプリケーションで、JavaScriptはあくまでも利便性のために補助的に使うものという認識がありましたが、さすがに最近では普通の業務系のSI案件でもテーブル表示や入力補助などで高度なAjaxライブラリーの使用が当たり前のように求められるようになりつつあります。サーバーサイドのJavaScript技術といったものもありますが、そういった新しい技術を使わないまでも、ごく普通にある程度大きなJavaScriptの作成が必要になってきているということです。
もちろん、JavaとJavaScriptはその名前にかかわらず、本来全く別の言語です。しかし、意図的に似た構文でロジックが書ける*1ため、兄弟の言語として認識している人も意外に多いのではないかと思います。しかし、使用できるライブラリーに違いがあるという点が一見してわかる最も大きな違いですが、基本的な言語の文法そのものでも意外に見落としがちな相違点がいくつか存在します。ここでは、その中でもJavaプログラマーの視点から見て特に気になったポイントを10個に絞り紹介します。
JavaScriptの文字列は基本型
見かけ上Javaの文字列とJavaScriptの文字列は文字列リテラルの引用符にシングルクオーテーションが使える点を除くと、そっくりに作られています。そのため、意外と見落としがちなことですが、JavaScriptの文字列は言語上はオブジェクトではなく基本型の値です。Javaの場合、文字列はStringクラスのインスタンスで、変数は他のオブジェクト型の変数と同様に参照型となるのですが、JavaScriptの場合は数値やbooleanと同様の基本型の値として振る舞います。
ですから、文字列の等価性を比較する場合は、以下のように==演算子や===演算子を適用することができます。
var str = "Hello"; var str2 = "Hello World".substring(0, 5); console.log(str == str2); // true
Javaプログラマー的な発想では、strとstr2は別々の文字列オブジェクトを参照しているため、最後の行の==による比較がfalseとなるように勘違いしそうですが、JavaScriptでは基本型として値の比較となるためtrueとなります。逆にJavaに戻った時にequals()を使うことを忘れてしまうとバグの原因となります。JavaとJavaScriptにおけるこの文字列の扱いの違いは十分に理解して正しく使い分ける必要があります。
なお、上記のコードサンプルでconsole.log()メソッドはデバッグ文を埋め込む場合に便利です。以前はalert()ダイアログを表示させてデバッグしていたものですが、最近のブラウザではconsoleオブジェクトが組み込まれているため、これを活用するのが便利でしょう。
(追記)JavaScriptの==演算子については、以下のエントリもご参照ください。JavaScriptで文字列の等価性を==で評価できるのは文字列が基本型だからなのか: 発火後忘失
基本型の違い
Javaにはintやdoubleなどの多数の基本型が定義されていますが、JavaScriptの場合基本型に相当するのは以下の3通り*2しかありません。
- boolean型
- number型
- string型
つまり、
- 基本型としてchar型はない
- 基本型として整数と実数の区別はない
- byte、short、int、longといった精度による区別がない
ということですね。なお、JavaScript変数の基本型は(Javaには無い)typeof演算子を使って調べることができます。
var strValue = "Hello"; var booleanValue = true; var intValue = 1; var doubleValue = 1.0; console.log(typeof strValue); // "string" console.log(typeof booleanValue); // "boolean" console.log(typeof intValue); // "number" console.log(typeof doubleValue); // "number" // 整数と浮動小数は区別がない console.log(intValue == doubleValue); // true console.log(intValue === doubleValue); // true
なお、Javaと同様にJavaScriptにも基本型に対応するラッパーオブジェクトとしてString、Number、Booleanというのがあります。これらのラッパーオブジェクトを使って文字列演算や型変換などのロジックを実行できます。
var strValue = "Hello"; var strWrapper = new String("Hello"); console.log(typeof strValue); // "string" console.log(typeof strWrapper); // "object" console.log(strWrapper.toUpperCase()); // "HELLO" console.log(strValue == strWrapper); // true console.log(strValue === strWrapper); // false
以上の例で、strWrapperの型はstringではなくobjectとなっていることに注意してください。だから、暗黙の型変換が行われる==比較ではstrValueと等価として判断されますが、型の変換を行わない===比較ではfalseと判断されています。
なお、JavaScriptでは通常ラッパーオブジェクトを明示的にnewで生成することは実際にはまれです。なぜなら、基本型に対してメソッド呼び出しの形で記述すると暗黙のラッパーオブジェクトに自動変換されるというしくみがあるからです。ですから、
console.log(strValue.toUpperCase()); // "HELLO" console.log("Hello".toUpperCase()); // "HELLO"
のような書き方ができます。この糖衣構文によって、一見JavaScriptがRubyやScalaのような基本型のない純粋なオブジェクト指向の言語に見えてしまうのですが、実はそうではなくて、内部でラッパーオブジェクトが一時的に生成されていると考えるのが正しい理解です。
undefined値の存在
JavaScriptでは値が存在しないことを示す特別な値としてundefined値があります。nullはJavaにも存在しますが、undefinedはJavaScriptに固有の型です。
var a; var b = null; console.log(typeof a); // "undefined" console.log(typeof b); // "object" console.log(a == b); // true console.log(a === b); // false
undefinedは上記のaのように初期値が代入されていない変数の値や型となっていますが、==比較ではnull値とのみ等価と判断されます。
console.log(a == ""); // false console.log(a == false); // false console.log(a == 0); // false console.log(a == null); // true
ところが、if文の条件式のように強制的にboolean型が期待される場所ではfalseに変換されます。
if (!a) { console.log("########"); // 実行される }
また、数値に変換される場所では、NaNに変換されます。
console.log(a + 3); // NaN
なお、NaNの型はnumber型でなくて、undefined型となります。
var c = Math.sqrt(-1); console.log(c); // NaN console.log(typeof c); // "number" console.log(isNaN(c)); // true
NaNはNot A Numberということなので、number型というのはちょっとおかしいように思えますが。
なお、undefined型は上記を含めて
- 未初期化変数の値
- 境界を超えてアクセスされた配列の値
- 関数で実パラメーターが渡されていない場合のパラメーター値
- 関数でreturnがされていない場合の戻り型
の場合に登場します。以下のエントリも参考になります。
JavaScriptのundefinedというクセ者のいろいろ - 風と宇宙とプログラム
配列の初期化の仕方、配列のメソッド
Javaの場合配列の初期化は中括弧{}で行いますが、JavaScriptの場合は角括弧[ ]で行います。
var a = [1, 2, 3]; var b = {1, 2, 3}; // エラー
なお、Javaと違いJavaScriptの配列は普通のオブジェクトと同様にメソッド呼び出しを使って要素を操作することができます。特に、push()などのメソッドを使って要素を追加することができます。JavaScriptの配列はJavaで言うところのArrayListに近いと思った方がよいでしょう。
正規表現の扱い
正規表現の扱いは、JavaScriptとJavaとで全く違っています。Javaの場合は、Javaで正規表現を扱うのは難しい - 達人プログラマーを目指してでも書いたとおり、かなり面倒なAPIを利用する必要がありますが、現在のJavaScriptではかなり強力な正規表現の仕組みが言語に組み込まれています。特にスラッシュを使った正規表現リテラルを使えば、RegExpオブジェクトを簡単に生成できます。
var regExp = /end$/; console.log(regExp.test("The end")); // true console.log(regExp.test("The end is near.")); // false
RegExpオブジェクトのメソッドを使う以外にStringオブジェクトのいくつかのメソッドで正規表現を利用できます。
var regExp = /\s/g; console.log("This is a test.".replace(regExp, "-")); // "This-is-a-test."
JavaScriptの正規表現については、以下が参考になります。
http://blog.wonder-boys.net/?p=294
{}のブロックがスコープにならない。代わりに関数がスコープ
JavaScriptではJavaと異なり中括弧のブロックが変数のスコープにはなりません。
function test() { var a = 3; console.log(a); // 3; { var a = "Hello"; } console.log(a); // "Hello"; }
以上のような独立したブロックだけでなく、if文やfor文のブロックもJavaと違って変数のスコープとはなりません。ここもJavaプログラマーは結構勘違いしやすいところですね。ただし、JavaScriptの場合は関数の中に関数を入れ子で定義できて、この関数が変数のスコープになります。
function test2() { var a = 3; console.log(a); // 3; function inner() { var a = "Hello"; } console.log(a); // 3; }
関連して、JavaScriptでは変数定義の巻き上げという現象があり、varで宣言された場所によらず、ローカル変数は関数の先頭から存在しているものとして扱われるので注意が必要です。
var a = "Hello"; function test3() { console.log(a); // undefined; var a = 3; console.log(a); // 3; }
以上の例では、ローカル変数aは関数の先頭から存在していることになるため、直感に反して関数の先頭からグローバル変数にアクセスできなくなります。Javaと違い、JavaScriptのローカル変数は関数の先頭でまとめて宣言するのが良いようです。
switch文の値に任意の型が使える
構文はJavaとそっくりですが、JavaScriptでは整数値に限らず、任意の値をcase式に指定することができます。でも、実質的に意味があるのは文字列や数値などの基本型の値です。
switch (value) { case "Hello": console.log("文字列 " + value); break; case 1: console.log("数値 " + value); break; default: console.log("その他"); break; }
なお、JavaでもようやくJava7からswtich文における文字列の比較が可能になりました。
Java7 体当たり/strings switch - 日々常々
最後に、JavaScriptのswitch文による値比較では===演算子のように厳密な型比較が行われ、暗黙の型変換は行われないことに注意する必要があります。つまり、"3"は3とは等しいとは見なされないということですね。値の比較に柔軟性を持たせられるRubyのcase構文とは違っているため、勘違いしないように注意が必要です。
定数の作成はfinalでなくてconst(だけど実質使えない?)
JavaScript1.5からはvarの代わりにconstキーワードを用いて定数を定義できます。
const PI = 3.141592;
しかし、残念ながらconstキーワードはIEではサポートされていないようです。ですから、実質的にはJavaScriptには定数はないと思っておいた方がよいかもしれません。
なお、オブジェクトの変更を禁止するObject.freeze()メソッドはありますが、constと違い定数そのものを定義できるわけではありません。つまり、このメソッドはオブジェクトに対して新しいプロパティの追加、削除や状態変更を禁止するために利用します。つまり、変数が参照するオブジェクトそのものの状態が不変になるのですが、それを参照する(ローカル)変数に対する再代入を防ぐものではありません。*3
関数が一級市民のオブジェクト*4
これはJavaScriptの最大の特徴の一つだと思いますが、JavaScriptでは関数そのものをオブジェクトとして変数に代入したり、パラメーターで受け渡ししたりすることができます。そして、関数オブジェクトはクロージャーとして動作します。いまさらだけど、Java言語にはクロージャーがない - 達人プログラマーを目指して
このポイントは、昔であれば上級者のみ理解していればよい内容だったのですが、今ではほとんどのJavaScriptライブラリーが関数オブジェクトを様々なところで利用していますので、避けて通ることはできません。
関数オブジェクトは関数定義を行う方法以外に関数生成式(関数リテラル)を用いて簡単に生成できます。
function calc(a, b, op) { return op(a, b); } var add = function(a, b) { return a + b; }; // 関数オブジェクトを生成して変数に代入 var subtract = function(a, b) { return a - b; }; // 関数オブジェクトを生成して変数に代入 var resultAdd = calc(1, 2, add); var resultSub = calc(1, 2, subtract); console.log(resultAdd); // 3 console.log(resultSub); // -1
なお、今時の他のスクリプト言語と違い、関数から値を返す場合にはreturn文が省略できないことに注意が必要です。(returnを忘れると戻り値がundefinedになってしまう。)
オブジェクトの生成方法とクラス
JavaScriptではオブジェクトリテラルを使って、オブジェクトを生成することができます。
var dog = { name : "ポチ", breed : "チワワ", bark : function() { return "わんわん"; } }; console.log(dog.name); // "ポチ" console.log(dog.breed); // "チワワ" console.log(dog.bark()); // "わんわん"
Javaと同じようにオブジェクトの中に含まれる値はプロパティ(フィールド)、関数はメソッドと呼ばれます。ここでは、オブジェクトリテラルを使って、プロパティとメソッドを最初から保持させていますが、オブジェクトに対しては後からこれらの要素を追加することもできます。
var dog = {}; dog.name = "コロ"; dog.breed = "ダックス"; dog.bark = function() { return "きゃんきゃん"; }; console.log(dog.name); // "コロ" console.log(dog.breed); // "ダックス" console.log(dog.bark()); // "きゃんきゃん"
このようにJavaScriptのオブジェクトではJavaの様にpublicやprivateといったアクセス制御がなく、また、プロパティ値だけでなく、プロパティの定義やメソッドも動的に追加できます。このことは、独自に生成したオブジェクトだけでなく、Stringなどの組み込みのオブジェクトに対しても実施できます。
var strObject = new String("Hello"); strObject.test = function() { return "======" + this + "======"; }; console.log(strObject.test()); // "===== Hello ====="
なお、JavaScriptにはJavaのクラスに相当するものがありません。そのかわりに、コンストラクタ関数という特別な関数を定義することができます。コンストラクタ関数は以下の特徴をもった関数です。
以下のように、コンストラクタ関数を定義すると、new演算子を使ってオブジェクトを生成できます。
// コンストラクタ関数 function Dog(name, breed) { this.name = name; this.breed = breed; this.bark = function() { return "わんわん"; }; } var dog = new Dog("ポチ", "チワワ"); console.log(dog.name); // "ポチ" console.log(dog.breed); // "チワワ" console.log(dog.bark()); // "わんわん" console.log(typeof dog); // "object" console.log(dog.constructor.name); //"Dog" console.log(dog instanceof Dog); //true
ここで、typeof演算子を使って調べると変数dogの型はobjectなのですが、instanceof演算子を使うとオブジェクトの型がDogであることがわかります。構文的にもJavaScriptの場合はコンストラクタ関数がオブジェクト変数の型と関連しており、クラスに近い役割を持っていることがわかります。なお、JavaScriptのオブジェクトを理解するためにはプロトタイプという考え方を理解する必要がありますが、ここでは説明しません。以下の本を参考にしてください。
まとめ
外見(言語名、構文規則、命名規約など)がJavaとそっくりなため、JavaScriptはJavaプログラマーであれば、何となく使えてしまいそうなイメージがあり、意味がわからないままWebや書籍のサンプルコードを貼付けて使っている人が多いかもしれませんが、詳しく調べていくと細かいところでかなり違っている部分があるため注意が必要です。最近は、社内システムのSI案件でもスクリプトとして短いコードを書くのみでなく、複雑なユーザーインターフェースの実現のため、相当の分量のJavaScriptコードを書かなくてはならないことも多く、一通り基本を勉強しておくべきかと思います。SI業界では多くの場合、Javaは習っても、JavaScriptをきちんと教えられる機会は少ないと思いますので。
JavaScriptの本はたくさん出ていますが、私が読んだ本としては、
- 作者: Shelley Powers,武舎広幸,武舎るみ
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/11/30
- メディア: 大型本
- 購入: 7人 クリック: 115回
- この商品を含むブログ (21件) を見る
- 作者: David Flanagan,村上列
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/08/14
- メディア: 大型本
- 購入: 52人 クリック: 1,011回
- この商品を含むブログ (271件) を見る
JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
- 作者: Douglas Crockford,水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/12/22
- メディア: 大型本
- 購入: 94人 クリック: 1,643回
- この商品を含むブログ (190件) を見る
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (76件) を見る
*1:多くのSI開発の現場ではオブジェクト指向設計など考慮せずifとforのみでロジックのみを書き下すという慣習があるため、特に差がないように見えてしまうところがあるのでは。
*2:後述のundefinedは除く
*3:C++言語で言うところのポインタの定数かポインタの指す先が不変なのかの違いと似ている
*4:JavaScriptでは関数が主役ということで、部分的に関数型言語風の記述も可能な側面はありますが、不変な変数という概念が弱いため、純粋に関数型のプログラムを作成するのは厳しいと思います。