はじめに
近年、「未経験でもエンジニアに」とか「文系出身でもエンジニアに」みたいな宣伝のプログラミングスクールがたくさんあり、私も会社の研修でそのうちのひとつにお世話になりました。
ただ、短期間でアプリケーションを作ることを目的にしているためもあってか、「基礎」というものにあまりフォーカスがあてられていないのでは、と感じ、これからプログラミングを学ぶ新人さんに向けて、また私のように「基礎」が足りていないと感じている新人エンジニアに向けて、Javaの「基礎」の入り口になるようなものを書こうと思いました。
「基礎」とは
これまで「基礎」という言葉にはすべてカギ括弧をつけてきました。
き‐そ【基礎】の意味
ある物事を成り立たせる、大もとの部分。もとい。「基礎がしっかりしているから、上達が早い」「基礎を固める」「基礎知識」
このエントリーでは、Javaの「基礎」を学ぶための第一歩として、
- JavaとJVM
- Hello World!が表示されるまで
の2つにフォーカスをあててみようかと思います。
JavaとJVM
まずJavaという言語そのものをみていきましょう。
Java
その前にまずプログラミング言語そのものを簡単に確認していきましょう。
プログラマーはプログラミング言語を駆使してコンピューターに指示を送ります。とはいえ、コンピューターは電気で動く計算機でしかありません。
(余談ですが、コンピューターはドイツ語でRechnerといいますがこれは「計算する」という動詞のrechnenからきています。本当に余談です。)
コンピューターは電気信号がオンかオフかの2つしか受け取ることができないので、昔のプログラマーは0と1の2進数を使ってプログラムを書いていたそうです。ちなみにその0と1で構成されたものはマシン語と呼ばれます。
2進数は桁数が多くなってしまうので16進数で記述することが多かったようですが、それでも人間にはフレンドリーではありません。そこで、マシン語が表す命令に対してニックネーム的なもの(「ニーモニック」と呼ばれる)をつけて「アセンブリ言語」というものが生み出されました。
しかしそれでもまだプログラムは人間にとってわかりやすいものとはいえず、かつ時代が進むにつれ要求されるプログラムの数も増加、複雑さも増していきました。そこで、より人間にわかりやすいプログラムの記述方法が生み出されていきました。
(ちなみにこのように人間にとってわかりやすいプログラミング言語は「高水準言語」と呼ばれ、一方コンピューターが理解しやすい言語は「低水準言語」と呼ばれます)
人間にとってわかりやすいということは、コンピューターにとってはわかりにくい、ということにもなります。
そこでプログラマーが書いたコードをコンピューターが理解できるようにマシン語に「変換」してあげなければなりません。それが「コンパイル」と呼ばれる作業で、そのコンパイルを行うものが「コンパイラ」と呼ばれます。
コンパイラは高水準言語で書かれたプログラムをコンピューターが理解できるように変換しますが、それはそのコンピューターに合わせてコンパイルをします。つまり別の環境のコンピューターはその変換されたマシン語を理解することができません。なのでまたそれ用に別のコンパイラが必要になります。
ここでようやくJavaの登場です。
Javaは1995年にサン・マイクロシステムズによって発表されます。高水準言語であるJavaは先ほど書いた通りコンパイルをしてマシン語にしてあげる必要がありますが、他の言語との違いはJVM(Java Virtual Maschine)という仮想マシンを使用する点にあります。
JVMはコンピューターのメモリを確保して起動します。そしてそのJVM上でプログラムを実行することができるので、コンピューターごとの差分を気にする必要がないのです。
もう少し具体的に説明すると、Javaは人間にとってわかりやすい形で記述され(ファイル名はhoge.java)、コンパイルを通じて中間言語と呼ばれるものに変換されます(ファイル名はhoge.class。中間言語という名前は正式なものではなく、便宜上そう呼ばれているようで、正しくはJavaバイトコードと呼ばれるようです)。
そしてその中間言語をJVMがロードし、一行ずつマシン語に変換して実行していきます。このように一行ずつ変換し実行するプログラムは「インタプリタ」1と呼ばれます。
つまり、Javaが実行されるまでに変換工程が2回あるわけです。(コンパイラとインタプリタによる変換)
JVM
Java環境を構築するときにJDKやらJREというものがありましたね。
JDKはJava Development Kitで、Javaを使って開発をするときに必要となるソフトウェア開発キットです。
そしてそのJDKに内包されるのがJRE(Java Runtime Environment)で、Java実行環境と呼ばれるものです。これはコンピューター上でJavaアプリケーションを動かせるようにするソフトウェア群で、JVMはここに含まれます。
JVMはスレッドとメモリから構成されます。
スレッドには処理が定義されていて、その処理を実行します。
メモリはヒープとネイティブに分かれています。
ヒープ領域にはアプリケーションのオブジェクトが格納され、必要なくなったオブジェクトは定期的にガベージコレクション(GC)によって整理されます。
ネイティブ領域はJVM自身が使う領域で、スレッドが処理中の情報を格納します(スレッドスタック)。
もう少し詳しく説明したいのですが、力尽きそうなので次いきます。
Hello World!が表示されるまで
それでは最後にHello World!が表示されるまでの過程を簡単に確認していきましょう。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
まずコンパイルをしましょう。
javacコマンドは、javaファイルをコンパイルし、classファイルを作成します。
javac HelloWorld.java
そうするとHelloWorld.classというファイルができます。それではまずはHello World!を表示してみましょう。
javaコマンドでJVMを起動し、Javaバイトコードで書かれているHelloWorld.classファイルを解析し実行してもらいます。
$ java HelloWorld
Hello World!
できました。
それではJavaバイトコードを確認していきましょう。
javapコマンドを利用すれば、classファイルを人間が読み取れる形式に変換することができます。
$ javap -c HelloWorld
Compiled from "HelloWorld.java"
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
public class HelloWorldから始まるこのクラスには2つ関数があり、1つ目がコンストラクタ、2つ目がmain関数のバイトコードに相当しています。もともとのコードにはコンストラクタはありませんでしたが、自動で生成されているようですね。
関数の中を見ていきましょう。
Codeは「インデックス:命令 #コンスタントプール番号」の3つから構成されています。コンスタントプールはJava仮想マシンにおいて、定数やシンボルを保存する領域のことですが、とりあえず見てみましょう。
javapコマンドに-vオプションを指定すると確認できます。
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello World!
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // HelloWorld
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 HelloWorld.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello World!
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 HelloWorld
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
それではmain関数を確認してHello World!表示の流れを見ていきましょう。
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
まずgetstatic命令ですが、#2を参照しています。これは
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
使用するフィールド、java/lang/System.outを取得しているのがわかりますね。
次のldc命令ですが、これは実行時にコンスタントプールから定数値をロードします。
#3 = String #18 // Hello World!
#18 = Utf8 Hello World!
Hello World!という文字列がここでロードされました。
そしてinvokevirtual命令ですが、
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
先ほどの文字列を引数にしてprintln関数を実行します。
最後にvoidをreturnして終了です。
お疲れ様です、これでHello World!が表示されました。
おわりに
自分が初めてちゃんとJavaを勉強するときに知りたいような内容にしようと心がけましたが、なかなか簡潔に、かつ要点をしっかりおさえて書く、というのは難しいですね。本当に一年弱前の自分はこんな内容で読みたいと思えるのだろうか、、、
とはいえ、この記事が誰かの役にたてば幸いです。これをきっかけにJavaの基礎を学びたいと思ってくだされば最&高です。
そしてもっとこんなかんじで発信していけるようにJavaの基礎をしっかり勉強していこうと思いました。
間違いがあればぜひご指摘よろしくお願いします。
-
コンパイル(compile)、コンパイラ(compiler)は英語の文法に従って分けられている一方、インタプリタは同じく英文法に従えばinterpretとinterpreterに分けられるはずだがその表記分けはされていないのかな? ↩