※注意
(2021.9.21)
まずは、私自身がかなり無知であるのに、あたかもJavaやその周辺技術を分かったかのように記事を挙げたことを、お詫びします。
ただし、記事の内容に関しては、原文のまま削除しません。
その理由は、現在の私のように、過去の事実関係があやふやなまま普段の業務に取り組んでいる人を晒すためです。
私自身にとっても、そこから出てくる指摘は学びになるので、助かります。
きちんと理解している方も、初心者の方も、【アンチパターン】として「この記事の著者の認識は良くない、怪しい部分がある」ことを理解して、必要であればさらに追加調査を行い、読み進めるようにお願いします。
なお、他社技術が入る部分だけは削除させていただきました
はじめに
「10年前の技術」に関する記事。
こんなテーマなら、さらに20年前の話から始めて、私の思い出話を語ろうかなと思う。
この記事では、私が知っている時代の「Java」を自由に語る。
語ろうとしている年代(1990年代後半)、私は当時小学生であり、当然SEなんて仕事はしていなかったが、密かに「システムエンジニアになりたい」という夢を抱いていた。
そんな私にとってJavaは、「プログラミング言語として学んだ2番目の言語」であり、
長い期間きちんと独学していた言語でもあるので、思い入れがたくさんある。
当時からしてみれば、オブジェクト指向やガベージコレクション、AWT/SwingなどのGUIライブラリ(なお今は死語)は非常に画期的な機能の一つであった。
当時は「Javaは文法的にもかっちょいいし、Cよりは遅いけど色々ソフトウエアが作れる言語なんだぜ~」なんて世間の風潮だったわけ。
実際、当時はJavaアプリケーションやJavaアプレット(これも死語だな)が当時人気だったFlash並みにWebで量産されていき、人気があった。
そんな今、そんな姿はどこへやら、既得権益(Oracle社)に飲まれた言語となっている。
ところで、当時あれほど「ポンコツ」と称されていた(Javaとなんとなく似ているけど全く別物の)スクリプト言語JavaScriptは、今や「開発者が好む言語No.1」になる時代。
まさかこんな時代が来るなんて想像もしていなかった。
注意
- ここに出てくるコードは、「20年前~15年前くらいのJavaのコード(を何となく思い出しながら書いたもの)」であり、化石のようなものだ。まず現代の設計に追いつかないものであるので、決して使用してはならない。
- 歴史に関する詳しい部分は不正確なので、例えば以下の良記事を参照にすること。当時の私は中学生~大学時代で、リアルで仕事しているわけではない。ビジネスや規格競争のような難しいことはようわからずにやってたからな。
- 本人も当時の状況を他の資料(Wikipediaなど)を使って割と不正確に伝えていますので、「これおかしい」とか「ここもっと調べて欲しい」ってのがあったら是非コメントください。
旧き良きJavaの遺産
20年前、Javaの黎明期
Duke
読者は、こいつの存在「Duke」を知っているだろうか。
ちなみに最近まで名前を知らなかった
このマスコットキャラクター、実は既にJDK Beta(1995)の頃には誕生していた。
当然のことながら、よく売れている書籍の中でこいつが描かれていたら、間違いなくJavaの書籍であることを確実に伝えている。
どうやってJavaを勉強したか
私がJavaを勉強したのは、丁度JDK 1.0~1.1くらいの内容をまとめてくれたプログラマーの書籍からである。
今でも間違いなく現存しているホームページに「浅煎り珈琲 Javaアプリケーション入門」がある。(なお化石)
今でこそ、無料有料問わず色んな分野に対する書籍・Webサイト・動画が多量にある時代であるが、
当時の私からしてみれば、プログラミングの基本がここまできっちり理解できるコンテンツは貴重で、
私にとってのデータアーキテクチャの基礎を今でも支えてくれている。
家電とJava
実は開発当初、JavaはIoTの先駆けだったことをご存知だろうか?
Javaは、当時として堅牢性を考慮した設計であり、同時にWeb技術への拡張性を兼ね合わせているため、
これをWebSocketを使ってHTTP/HTTPS/FTPプロトコルなどに接続してしまえば、あら不思議IoTモジュールの完成、そんな未来を描いた言語だったのだ。
Web技術の中心となった現代のJavaであるが、Javaは、元々家電の制御のために作られた技術であった。
それは、当時C++はあまりにも柔軟性がありすぎて制御の安定性に問題があり、現代ほど電気製品の制御に向いている技術ではなかったためである。
そこで、JavaはC++の概念を整備して堅牢性を重視して設計された。
Javaの概念は型制約の厳しさを残してC#に引き継がれている。
Java仮想マシン(JVM)と互換性
Java仮想マシンという概念も、現在で言うところのクロスプラットフォーム開発やVirtual Desktop、Dockerなどの仮想化環境の考え方のベースとなる素晴らしいものであった。
一般に、プログラムの実行される方式はインタープリタ方式とコンパイラ方式がある。
- インタープリタ方式は、人間の書いたコードをそのまま実行する方式
- コンパイラ方式は、人間の書いたコードを機械語に変換する方式
インタープリタ方式が現在の多数の高級言語に該当し、
コンパイラ方式の代表格がC++であることは言うまでもない。
そして、そのJavaは、どちらの方式でもない中間コード方式を取っていた。
これはコンパイルするときに、Java仮想マシン(JVM)に理解できる形式で変換するものであり、
それで生成された中間言語は、JVMが責任を以て実行するので、どのような環境でも実行出来る強みがあったのだ。
当然、家電のような組込み用途においても、このような設計が活用できるのが有意義には違いない。
Swing
GUIに関しても規格化が行なわれるような流れとなっていて、JavaにおいてはSwingと呼ばれるライブラリが並んだ。
当時としてはLook&Feelに配慮された綺麗なGUIで、JVMの性質上どのようなプラットフォームでも動くのが強みだった。
実際、Swingの画面とmfcの画面を比較してもらえれば分かると思う。
mfcはC++でWindowsでしか書けないのに、よくも悪くもWindows的なGUIであるのに対し、Swingはこれがあらゆるプラットフォームで動くんだからすごいすごいってなると思う。
Swing
GUIの規格化とレイアウトマネージャー
さらに、このSwingとはちょっと違う話題だが、レイアウトマネージャの概念も、Javaをユニークにする一つの概念であった。
実はレイアウトマネージャに関してはきちんと対応したC#フレームワークは、WPFまで存在しないのだ。
つまり、当時のWindowsアプリケーションはほぼ例外なく絶対座標系でGUIを表示していた。
現在でもこの絶対座標系を禁止するような正式な解決策はなく、私もWindowsアプリ捨ててブラウザ使いたいって気持ちになってしまう。WPFめんどくせえんだよなあ
そしてそのWPFも難しすぎるってんで、それをHTMLとJavaScriptで記載してアプリケーションを作れるようにしたelectron.jsが割といま元気であるけれども、こうした時代に来るまでMicrosoftで作られた製品がユニバーサルデザインを意識した設計になることはなかったのである。
(tkinterやQtを見ていると、もしかすると当時でもユニバーサルデザインに対応したものはあったかもしれませんが。)
特に、以下のようなGridBagLayoutと呼ばれるレイアウトは難しかったが非常に便利であった。
HTMLで言うところの
<table>
<tr>
<td colspan=2>AA</td><td>BB</td>
</tr>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
</table>
に対応している機能なのだが、多少その仕組みが複雑とは言えどのようなレイアウトも再現出来たのには一定の効果があったように思える。
また、同じGUIをカードを切るように前後ろに配置するCardLayoutと呼ばれるレイアウトも便利であった。
これも、現在のHTML5ではインラインで代用される概念だと思うが、こういうのがデフォルトについていたJavaはレイアウトの考え方に対しても素晴らしい部分があったと思う。
アプレットとFlash
この時代を象徴するJavaで出来たものと言えばアプレットであるだろう。
けれど、実はJavaの中でアプレットは初期の頃はJavaの目玉技術として採用されていたものの、2017年にはもはや表示されなくなるなどの憂き目に遭っている。
それでも、当時はすごかったのだ。個人のHPにJavaアプレットが大量に生成された。
今ではJavaScriptを使えば容易にゲームくらい作れる時代(私にそんな力はないが)だが、当時流行したFlash同様、JavaはプラグインをHTMLに埋め込む形式として流行した。
↑のような本が山ほどあった時代だったわけです
Javaアプレットの最大の強みは、JVMが責任もってブラウザの動きを管理してくれたことになる。
今でこそフロントエンド⇔バックエンドという用語は一般的なものになっているが、当時こうしたゲームを作ろうと思ったらプラグインを埋め込む方式が主流なのであった。つまり基本はフロントエンド側で完結するスタイルなのである。
そんなアプレットのHTMLは、どこでもこんな感じに書籍に書いてあったわけだ。
<html>
<title>Java Applet</title>
<body>
<applet src="myapplet.class" width=640 height=480>
</applet>
</body>
</html>
この書き方自体、今のJavaScriptのHTML5に通ずるところがある。
もちろんこんな感じでプラグインのプログラムを1個だけ設置するなんてのはないのだが、idやname、classを使って上手いこと識別子を与えて、そこに表示したいプログラムをダイレクトに組み入れる考え方は似通っているのだ。
Javaはモバイル開発の元祖
Javaといえば、Androidのモバイルアプリケーションの分野で未だに現役であるが、既にもうこの時代から「携帯に使おうか」の流れはあったのだ。本当、Javaは恐ろしい子。
当時のモバイルフレームワークは多数あったが、有名どころは
- CLDC
- MIDP(Mobile Information Device Profile) : Vodafone, au ezアプリ用途
- Doja : Docomo i-modeアプリ用途
などであろう。
Vodafoneという単語自体が懐かしいな。私は結構長年まで、Vodafoneユーザであった。
私の記憶では、自分自身の演習の目的でMIDPを頻繁に使っていたので、それを思い出すためにソースコードがどんな感じだったか紹介すると、
WebではAppletと言うように、MIDPではMIDletと呼ばれるオブジェクトクラスを定義する。
めっちゃ懐かしいコードだと本気で思った()
Javaにおいて、結構Appletが基本で派生したモバイル開発は多いのかなと印象。
また、このことでAppletで学んだ知識がそのまま大体Javaアプリ開発に活かせるのも大きなメリットだった。
一方、下記にもあるようにMIDletからオーバーライドしたメソッドでしか処理が出来ないので、設計に対する自由度はさすがに現代と比べるとかなり劣る。
Appletは、ブラウザのプラグインに埋め込んでいたのでその範囲の中では自由度が高かったのですが、モバイルアプリは「モバイルアプリの一つのウィジェット」だけが切り出されているせいで、出来ることが少なくて「使いにくいな~」という印象はどうしても残った。
またご存知のように、昔のガラケーは画像容量が少ない!ゲームとか作ろうとすると、画像リソースの問題にも限界が・・・。
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class HelloMIDlet extends MIDlet implements CommandListener {
public HelloMIDlet() {
...(GUI初期化処理)...
}
public void startApp() {
...(動作start時の処理 MIDletのオーバーライド)...
}
public void pauseApp() {
...(Pause時の処理 MIDletのオーバーライド)...
}
public void destroyApp(boolean unconditional) {
...(終了時の処理 MIDletのオーバーライド)...
}
public void commandAction(Command c, Displayable s) {
...(GUIで登録したEventListenerの処理 CommandListenerから継承)...
}
}
javadocでドキュメント作成を簡単化
この記事をまとめている最中、重要なトピックであるjavadocのことを触れるのを忘れていた。
本当、書けば書くほどJavaのネタが出てくる。それだけ将来性が期待されていた言語なんだろうなって思う
Javaを少しでもやっていれば、このAPI仕様書には必ずお世話になっているはずだ。
しかし、私の過去においては若干これはトラウマだ。
継承の概念も少しずつ学んでいく時期だったので、分からないクラスはとにかく索引から探す!!!なんていう無謀な作業に取り組んでいた記憶がある。
当時はひたすら検索をかけまくったので、Ctrl+Fはショートカットキーとして覚えろと言わなくても体が覚えてしまった。
↑こんなところから探せとか言われたらマジで発狂する(なお現実)
さてこのjavadocも、ソースコード管理上、大変画期的な仕組みである。
Sun Microsystems社の社員は、この膨大なJavaのライブラリに一つ一つWebページを用意し、機能説明を準備しただろうか?
しかもリリース期間が1~2年という状況で。
当然そんなことはしないわけで、ソースコードにコメントの型を決めてそれを自動生成していたのだ。
javadocでマニュアルに追記する場合、例えばこんな感じで書いたりする
(外部サイトより引用)
public class Sample01{
/**
* サイズの設定
* @param width 幅
* @param height 高さ
*/
public void setSize(int width, int height){
}
}
そしてコンパイルするときに、javac -d Sample01.javaなどと書いて実行すると、先ほどのAPI仕様書が自動生成されるのだ。
この考え方は特にJavaのプログラマーにとって本気で感動させられる仕様であった。
(もちろん現在では割と日常的なものであるのだが)
API仕様書の作成の作業が一つ減り、コードレビューする際もこのjavadocを吐き出せばレビューが出来るのが素晴らしい。
Collection Framework
Collection FrameworkはC++のSTLを真似したものであるが、これもJavaでは独自の仕様となった。
実際、JavaのSTLで採用されているVectorはC++をベースに設定された可変長配列であり、基本になっているようである。
ところが、当時Javaはジェネリクスが存在しなかった。
一応、可変長配列として有名なArrayListなんか
ArrayList list = new ArrayList();
と書いていたのだが、型指定の難しさは課題にあったのだ。それで2005年のJ2SE 5.0以降のアップデートでジェネリクスが生まれていた。
ちなみにそういう面では、他言語とは多少出遅れている面もあったと思われる・・・と思いきや、全く同じ時期の2005年にC# 2.0が出来たのだが、この時点でC#はジェネリクスに対応していたようだ。
私の中では、C#でジェネリクスを積極的に扱えるようになったのは、LINQが誕生するC# 3.0だったように思える。
革命的な文法 (1) インスタンスを渡す
インスタンスの概念は、C++の左辺値参照が基本となっている。そしてその左辺値参照はどうも1988年に構想され導入されたもので、この概念自体はC++がベースとなっているようだ。
しかし、C++の左辺値参照には「インスタンス」の概念はなく、実体がどこかにいなければそのデータは無効となる。
// originalはSomeClass型の実体のあるデータ
// 参照は常に実体を持たないといけない。
SomeClass& obj_ref = original;
またインスタンスに近い概念であるポインタは、nullptrを許容するのでそれが制御上の問題に繋がる。
// nullptrが許容されてしまう
SomeClass* obj_ref = nullptr;
// つまり、途中でこのような処理がなければ実行時例外が起きる
if( obj_ref == nullptr) {
...(処理)...
}
// originalはSomeClass型の実体のあるデータ
// 参照は常に実体を持たないといけない。
obj_ref = &original;
ところで、Javaのルールに基づいてインスタンスを発明したことで、抜本的にこの問題が解決されているのだ。
つまり、インスタンスはそこでは常に参照を渡し続けると出来るのだ。
SomeClass obj = new SomeClass();
// ☆ obj2を生成するときに、objを参照渡し
SomeClass2 obj2 = new SomeClass2(obj);
だが私の印象だが、これが原因で、当時、C言語/C++を中心に開発してきたエンジニアは、JavaやC#、objective-Cなどのオブジェクト指向型の言語で挫折する傾向が強かったように思う。
☆の部分で、まさかobjが変更される可能性があるとは思うまい。C++の言語的には、
SomeClass2* obj2 = new SomeClass2(obj);
と書かれりゃ、
class SomeClass2 {
public:
SomeClass2(SomeClass obj) {
...(処理)...
}
...(以下省略)
};
と勘違いする人と、
class SomeClass2 {
public:
SomeClass2(const SomeClass& obj) {
...(処理)...
}
...(以下省略)
};
と何となく意識づけ出来る人と分かれてしまうからだ。
その対策として、C#では値クラスが導入されたり、C++でも構造体とクラスは別の役割をするように調整されたりしたわけだが、私の印象ではバリバリポインタを使ってくるエンジニアほど、ここのところは弱いという印象があった。
一つの障壁になってしまっていたのが、悲しい。
革命的な文法 (2) インタフェースとポリモーフィズム
また、Javaにおいてインタフェースクラスも重要な文法表現である。
C++では、多重継承がサポートされている。
class SubClass : public SuperClassA, public SuperClassB {
...(実装)...
};
この多重継承の最大の利点は、様々なクラスの型変換を許容することにある。このように書けば、
// originalはSuperClassAで生成されたオブジェクト
auto obj1 = dynamic_cast<SuperClassA*>(original);
と書くことを許容するが、このdynamic_castはSuperClassB*に対してはnullptrを返却する。
それをJavaでは、
- 一般的なクラスの多重継承は禁止とする。
- 多重継承はインタフェースのみ許容し、インタフェースに存在するメソッドは全て抽象化され、オーバーライドしないといけない。
のルールを追加した。このようにしたことで、
interface IntarfaceA {
...(実装)...
}
interface IntarfaceB {
...(実装)...
}
public class SubClass extends SuperClass implements InterfaceA, interfaceB {
...(実装)...
}
と書けるようになり、さらにポリモーフィズムに破綻せずに、
// originalはSubClass型のインスタンス
interfaceA obj_a = original;
interfaceB obj_b = original;
とする記載を許容することが出来たのだ。
こうすることでの最大のメリットは、オブジェクト指向の基本的な考え方であるis-a関係を破綻させていないところにある。
GoFのデザインパターンも、is-a関係あってこそ成立するものである。デザインパターンがオブジェクト指向の基礎教材となり、色々なデータ構造を表現できるようになったのは言うまでもない。
これは大きなポイントであり、古きJavaを語る上でマルチスレッドやEventListenerを使うときに重要な部分となった。
EventListener型の発明 ~ Delegate型との大きな違い ~
このEventListenerを聞くのは、現在では一部のJavaScriptネイティブでやっている人しかいないと思われるが、これも非常に画期的な仕組みだった。どう画期的かと言うと、イベントリスナークラスを定義してそのGUIに登録するだけでイベントの挙動を表せてしまうわけだ。
確かに、この仕組みはややこしいものであったが、このことによりイベントを共通のオブジェクト化する考え方が一般的になったのもある。
GUIにActionListenerと呼ばれるインタフェースを用意しておき、そのインタフェースを継承したクラスを開発者が作り、それを登録する。
例えば同期のC#ではこうした設計をするとき、delegateを採用するだろう。Windows Formsの例であるが
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("ボタンがクリックされました!");
}
}
という感じで、あくまでデザインビューワの中にbutton1_Clickを仕込ませて構成する。
ここで、フォームに存在するボタンGUIは別にメソッドOnClickを用意しておき、実際はdelegateでbutton1_Clickを実行するように仕事を投げる。
この仕組みは一見快適で、逆にWindowsアプリケーションの仕組みが楽すぎて、他のフレームワークに移行できない人が増えている原因になっているのだが、もう一つ大きな問題を抱えている。
それは「Formの実装の中に処理が潜んでしまうこと」だ。
つまりこうしたコードを書いてしまうと、MVPパターンで言うところのフォームのViewの中に、ボタンイベントであるPresenterの処理が混在してしまう。
技量がなくモジュール分割の習慣がついていないエンジニアなら、下手したらそのPresenterの中にModelとViewを一緒にごちゃ混ぜにして書く惨事すら招くのだ。もはやそんな状況では、MVPモデルなんて言うものは存在しないだろう。
それをJavaではEventListenerの形式で回避していて、
class MyActionListener implements ActionListener {
MyApplet applet;
public MyActionListener(MyApplet applet) {
this.applet = applet;
}
//ActionListener インタフェースの実装
public void actionPerformed(ActionEvent e) {
...(Modelの処理やViewのプロパティに該当する値を変更する)...
}
}
public class MyApplet extends Applet {
private Button btnBlue;
private MyActionListener actionListener;
... (略)...
public void init() {
btnBlue = new Button("blue");
actionListener = new MyActionListener(this);
btnBlue.addActionListener(eh);
}
}
こうすることで、少なくとも(Viewの一部+Modelの一部の処理)をListenerが、(Viewの処理)をAppletが担ってくれるようになるので、
設計が分かっていれば少しは上手く出来るようになる。
ただ、多分MVPモデルをきちっと意識して設計した人がいたかはかなり怪しい。解説書を読んでもそういうことには触れなかった。
実際、WPFが出てからMVVMに対する布教が進んできたという印象があるので、当時のプログラマーも要素分割の概念があったかはちょっと分からない。私は(当時)あくまでホビーユースだったから
10年前の自分に伝えたいこと
実は、こうした20年前の技術があったり、色々と画期的な設計を目指したJavaであったが、ある時期からそのJavaをやるのをやめたのであった。
その理由のきっかけは色々あるが、
- Java 6からのアップデートがあまり行われなかったこと
- プラグインを使う方法がセキュリティセーフではなかったこと
- Oracle社の買収~~
などが挙げられる。
まあ、アルゴリズムの分野に関して言えばJavaで作られた成果物を結構見るし、それなりに上手く動くので、教育的な部分に関してはそれなりに使えた面はあったけれど、既に当時から限界な感じはちょっと見えていたのであった。
また、Javaを躍進させたモバイル開発の業界は確かに今も堅調だと思うのだけど、
少なくともPCを中心にやっていく私の世界だと、当面手を引かないといけない状況になっていたのも事実だ。
それが現在、安定期も終わり徐々に衰退期に近づいているような。
Oracle社が(アップデートを重ねることで)独自のDBシステムと共に既得権益化したこと、
openjdkはGPL化され、Javaで書いたコード以外のライセンス汚染が問題になること
を誰が想像しただろうか?
もし、私が10年前の自分にいえるとすれば、
「今すぐモバイル開発にさらに時間を集中させるか、C++の制御技術やパターン認識の知識をもっと深めろ」
と言っているかもしれません。