1.はじめに
前回の記事では、主に日本語の解説を中心に内容を整理しましたが、今回は海外の記事を参考にしています。海外の筆者は物事を明確に述べる傾向があり、学びが多いだけでなく、読み物としての面白さも感じられたため、本記事で取り上げることにしました。
2.何故JavaとTypeScriptを比較するのか?
私が両者を比較しようと思った理由は、開発でTypeScriptとJavaを使用する機会があったこと、そして「TypeScriptとは何か」という素朴な疑問を持ったことにあります。当初は、「JavaScriptに型(制約)を事前に定義できる言語」と説明を受け、「それはJavaに近いのではないか」と感じました。
この疑問について調べてみると、同様の内容が数多く見つかり、多くの人が同じような疑問を抱いていることが分かりました。
また、日本語の記事ではあまり見られなかったものの、海外の記事ではこの点について明確な意見が述べられており、非常に参考になりました。
At first glance, the answer is simple. TypeScript "lives" in the browser. Java lives everywhere else. So they don't compete at all. End of the article.
「TypeScript(以下:TS)はブラウザ(画面)の中で生き、Javaはそれ以外の場所で生きています。つまり両者は全く競合しません。これでこの記事は終了です。」と言われています。 そうですよね。主として生きる場所がフロントエンドとバックエンドで用途が違うんですから。
しかしこれには続きがあります。
However, TypeScript is an attractive language for people with a Java background. To them, the question is slightly different. Is TypeScript an easy language to learn? Plus, there's Node.js. Using JavaScript on the server has become surprisingly popular. TypeScript compiles to JavaScript, so it has become a direct competitor to Java.
「TypeScriptはJavaのプログラマにとって魅力的な言語であり、『学びやすいのか』という点に関心が集まっている。また、Node.jsの普及によってサーバーサイドでJavaScriptを利用することが一般的になった。TypeScriptはJavaScriptにコンパイルされるため、Javaと競合する存在になりつつある」と述べられています。
この点を踏まえると、Node.jsの普及によって、従来はフロントエンド中心だったJavaScript(およびTypeScript)がサーバーサイドにも進出し、結果として「TypeScript(JavaScript)とJavaが同じ領域で比較される状況」が生まれたと理解できます。
そのため、前回の記事で述べた「JavaScriptとの互換性はJavaとの比較にはあまり関係がないのではないか」という考えはやや不十分であり、実際にはサーバーサイド利用を前提とすると重要な要素であると認識を改めました。
3.Javaに対するTypeScriptの優位性について
前回の記事と多少重複する内容です。「型推論」の話をします。(前回のものを少し改善を試みた内容になります。)
TypeScript benefits from its powerful type inference. This makes for a fairly relaxed programming style unless you exaggerate it. Java forces you to declare trivial facts over and over again. It doesn't suffice to know that 42 is an integer number, you have to jot it down:
//こっちがJava
int fourtyTwo = 42;
//こっちがTS
let fourtyTwo = 42;
Javaは int a = 42; のように型を明示させるが、TSは let a = 42; で済む。
In this particular case, that's not very exciting. We've replaced a three-letter keyword by another three-letter keyword. Things get interesting with advanced types, such as arrays and maps. Since Java 7, we've got a limited type inference called target typing. Even so, Java is a bit ceremonious:
// Java 7 以降:
Map<String, String> trafficLights = new HashMap<>();
// Java 6 より前:
Map<String, String> trafficLights = new HashMap<String, String>();
//TSはもっとシンプル
let trafficLights = new Map<string, string>();
//TSはさらに配列やマップのリテラルを使えばもっと良くなる
let colors = ["red", "yellow", "green"]
// Array<string>として推論
let trafficLights = ["red" : "stop",
"yellow": "be careful",
"green" : "go"];
コードブロック内のコメントとして一部日本語訳を補足していますが、「配列やマップのような高度な型になると違いがより顕著になる」という点が特に印象に残りました。Java 7以降ではターゲット型付けによる限定的な型推論が導入されているものの、それでもJavaはやや「儀式的(冗長)」であると感じられます。
この点に関しては、特に配列やMapのコードにおいて顕著であり、TypeScriptの記述はより直感的で、コードを読むだけでも意味を把握しやすい構造になっていると感じました。
4.陥りやすい罠(落とし穴)
「罠」と聞くと強い印象を受けるかもしれませんが、本記事ではJavaプログラマがTypeScriptを学ぶ際の注意点としてまとめています。
海外の記事では、こうしたポイントが「pitfalls(落とし穴)」と表現されることが多く、翻訳の影響もあって「罠」と訳されていると考えられます。
Funny thing is that type inference is the cause of a common mistake during the first couple of days. Java always puts the type before the name of the variable. TypeScript allows you to optionally define the data type, but it puts the type behind the variable name. For instance, method definitions are sort of the other way round:
// Java
public int multiply(int a, int b) {
return a * b;
}
// TypeScript
public multiply(a: number, b: number): number {
return a * b;
}
型の記述位置についての説明になります。 Javaは 型 変数名 だが、TSは 変数名: 型。 あべこべになりやすいため、慣れるまでに時間がかかるという旨の説明をしています。
また、別の記事では
A class might not be a class TypeScript classes are great for solving complex business problems in an object-oriented (OO) manner. In client-side applications, data often comes from a backend service as a JSON payload, which can be transformed into an interface type in TypeScript. However, I have noticed a common mistake where a class is used instead of an interface type. As an experienced OO programmer, I tend to implement functions that operate on the properties of the interface type within a class. However, this approach does not work since, during runtime, only a JSON object is available, which does not have the added functionality of the class.
内容が長くなるため要約すると、バックエンドから受け取ったJSONデータをTypeScriptのクラスにキャストしても、それはあくまで「データ構造(インターフェース相当)」として扱われるに過ぎず、クラス内で定義したメソッドは実行時には存在しません。そのため、クラスが本来の意味で機能しないケースが多いとされています。
この問題への対策としては、JSONデータを扱う際にはinterfaceを使用し、ロジックはクラスに持たせるのではなく、データを引数として受け取る純粋な関数として定義する方法が安全とされています。
class ValueProvider {
value: string;
getValueOrDefault(defaultValue: string): string {
return this.value ? this.value : defaultValue;
}
}
const json = {
value: 'value'
};
const valueContainer = json as ValueProvider;
console.log('Get value: ' +
valueContainer.getValueOrDefault('undefined')
// throws "Uncaught TypeError: valueContainer.getValueOrDefault is not a function"
);
//メソッド getValueOrDefault() を追加しまうので
//例えトランスパイル(コンパイル)が通ったとしても、実行時にエラーとなる
Function references are great, but… One neat feature of Typescript is keeping the this keyword inside class functions. With Javascript only, this would be the caller’s context instead of the class instance. Once knowing this feature, you start using Typescript’s arrow functions everywhere. But most Typescript beginners I met do not use arrow functions when creating function references. Instead they return the reference as this.myFunction. In order to keep the this within the function here as well, you have to use the arrow function again: () => this.myFunction().
TypeScriptの特徴の一つとして、クラス内でthisのコンテキストを維持しやすい点が挙げられます。
一方、JavaScriptではthisはクラスのインスタンスではなく「呼び出し元のコンテキスト」に依存するため、意図しない挙動になることがあります。そのため、thisを正しく維持する目的でアロー関数が多用される傾向があります。
しかし、初学者の場合、関数参照を返す際にアロー関数を使用せず、単にthis.myFunctionのように記述してしまうことがあります。この場合、呼び出し時のコンテキストによってはthisが失われてしまいます。
これを防ぐためには、() => this.myFunction()のようにアロー関数を用いてラップし、thisを明示的に保持する必要があります。
class FunctionProvider {
private message = 'Hello world';
logMessage() {
return console.log(this.message);
}
getFunctionRef() {
return () => this.logMessage();
}
getFunctionRef2() {
return this.logMessage;
}
}
const functionProvider = new FunctionProvider();
functionProvider.getFunctionRef()(); // console output: Hello world
functionProvider.getFunctionRef2()(); // console output: undefined
//アロー関数を使用せずに関数を参照すると、重大な影響を及ぼす可能性があります。
In Javascript, there is no concept of enums, and Typescript enums are just an abstraction of runtime values. Per default, an enum’s value is a number, given by the order of the enum value declarations. Thus, changing the order of the enum values will lead to inconsistencies with previous stored data, and matching an enum value’s name with a JSON string will fail. Both problems can be solved by setting explicit values for enums. Another solution is using types of string constants. Such a type is like a string, but any value not matching the defined constants, leads to transpiler errors.
TSのEnumはデフォルトで数値(0, 1, 2...)として扱われます。要素の順番を変えると値が変わってしまい、DB保存やJSON連携でバグの原因になります。 解決策として、Enumに明示的な値を設定することで解決できます。 もう一つの解決策として、文字列定数の型を使用することです。この型は文字列のように振る舞いますが、定義された定数に一致しない値はトランスパイルエラーになります。
const input = 'APPLE';
enum Enum1 {APPLE, PEAR}
console.log(input as Enum1 === Enum1.APPLE); // transpiler error (or "false" console output in earlier versions)
enum Enum2 {APPLE = 'APPLE', PEAR = 'PEAR'}
console.log(input as Enum2 === Enum2.APPLE); // true
type Type = 'APPLE' | 'PEAR'
console.log(input as Type === 'APPLE'); // true
Enumのオプションには、常に具体的な値を設定すると良い。
TypeScriptのEnumをより深く理解するために、シンプルな例をトランスパイルした後のJavaScript出力結果を確認することが推奨されている。
4.1 JavaとTSどちらを使うべき論争について
When to use which? Most of the time, the answer is simple: use TypeScript for the browser and Java for everything else.
Node.js allows you to use TypeScript on the server and the desktop, too. If you've got experienced JavaScript or TypeScript programmers, that's a good option. Node.js is a decent web server, and Electron opens the world of desktop programming to you. However, if your team consists of proficient Java programmers, stick to Java. The tool chain and JavaEE currently are more mature than their TypeScript counterparts. TypeScript and JavaScript are catching up quickly, so my answer might be different next year. Mind you: the programming model of a Node.js server is simpler than the programming model of a JavaEE application, which may be a plus with respect to maintenance.
Tools like TeaVM and GWT allow you to use Java in the browser, too. However, I don't think that's a good option. It might be an option if the alternative language is JavaScript. But if you're choosing between TypeScript and Java, opt for TypeScript as a browser language. The extra effort to learn the language isn't big enough to justify using a Java-to-JavaScript cross-compiler.
(画像は自身で整理したものですが、)上記の観点を踏まえると、「ブラウザ開発であればTypeScript、それ以外の領域ではJavaが適しているケースが多い。また、Node.jsを利用することでサーバーサイドでもTypeScriptは選択肢となる。特に、JavaScript/TypeScriptに習熟したエンジニアがいる場合には有力な選択肢となる。一方で、Javaに習熟したチームであれば、既存のエコシステムの成熟度を考慮し、Javaを継続して利用する判断が合理的である」と言えます。
このように整理すると、最終的には両者の優劣ではなく、プロジェクトの要件やチームのスキルセットに応じて選択する、いわゆる「適材適所」という結論に至ると考えられます。
5.終わりに
調査の過程では翻訳を介しているため、解釈に多少の誤りが含まれている可能性はあります。それでも、解説の中には知らない単語や知識不足を感じる部分が多く、文量自体はそれほど多くないものの、理解には相応の時間を要しました。
一旦、JavaとTypeScriptの比較については区切りとし、今後さらに知識が深まった段階で見返し、誤りや認識のズレがあれば適宜修正・加筆していく予定です。
次のテーマとしては、PostgreSQLについて調べていきたいと考えています。
参考資料
Comparing TypeScript to Java
TypeScript for Java developers: Common pitfalls
TypeScript — nominal typing and branded types
Front-End vs. Back-End
Java vs. TypeScript/JavaScript: Beyond Declarations — A Deeper Dive
