プログラムの語源は【pro=前もって】【gram=書かれたもの】です。

ですからプログラムは決して思いつきで書いてはいけません。

プログラムを作成する際には、計画性と構造的なアプローチが不可欠です。良いプログラムは、前章でUMLを学んだように事前に詳細な設計を行い、機能ごとに分けて段階的に開発されるべきです。

また、次章で学ぶようにアルゴリズムを考えて効率の良いコードにすることも重要です。

さらに、本章で解説するように各プログラミング言語の特性を理解し、体系的にアプローチすることが、効率的で信頼性の高いプログラムを生み出す鍵です。

1. プログラミング言語の選択

プログラミング言語は、その特性や設計哲学に基づいてさまざまな方法で分類することができます。以下に、一般的に行われる分類の一部を列挙します。

1.1 プログラミング言語の分類

世代による分類

  • 第一世代言語(1GL)は、機械語とも呼ばれ、コンピュータが直接実行できる言語です。バイナリ形式(0または1)で書かれ、人間が読み書きするのは困難でした。
  • 第二世代言語(2GL)は、アセンブリ言語とも呼ばれます。機械語の代わりに短い記述子【mnemonics】を使用し、いくぶん人間が読み書きしやすくなりました。だがまだハードウェアに密接に結びついていました。
  • 第三世代言語(3GL)は、現代の多くのプログラミング言語(Java, C, C++, Pythonなど)に該当し、より抽象的な概念と構造(ループ、条件分岐など)を導入しています。

パラダイムによる分類

プログラムのパラダイムとは、プログラムの考え方、方法論の枠組みのことです。

  • 手続き型言語は、手続き型または命令型言語として知られ、コードが実行される手順を順番に指定します。C, Pascalなどが手続き型言語の例です。
  • オブジェクト指向言語は、オブジェクト指向プログラミングをサポートし、属性(データ)と振る舞い(操作)をオブジェクトという単位にまとめます。Java, C++, Pythonなどがオブジェクト指向言語の例です。
  • 宣言型言語SQLなどは求める結果を指定することに重点を置いた言語です。

他にも関数型言語や論理型言語があります。

レベルによる分類

  • 低水準言語は、ハードウェアに近いレベルで動作し、メモリ管理などの詳細な制御が可能です。CやC++などが該当します。
  • 高水準言語は、より人間の自然言語に近い概念を用いて、より抽象的なレベルで動作します。Java, Python, Rubyなどが該当します。

これらの分類は排他的ではなく、多くの現代の言語は複数のパラダイムをサポートしたり、異なるレベルの抽象化を提供したりします。例えば、Javaはオブジェクト指向と手続き型の両方のパラダイムをサポートし、JavaScriptはオブジェクト指向と関数型の両方のパラダイムをサポートします。

1.2 コンパイラ言語とインタプリタ言語

コンパイラとインタプリタは、ともにプログラミング言語を機械語に変換するツールですが、その動作の目的と方法が異なります。

コンパイラ

コンパイラはソースコード(書かれたプログラム)をすべて取り込み、それを一度に機械語に変換します。この過程は「コンパイル」と呼ばれ、生成された結果は「実行可能ファイル」または「バイナリ」と呼ばれます。

コンパイラは、すべてのコードを一度に解析するため、最適化を行う余地があります。コンパイルされたコードは、一般的には実行速度が速くなる可能性があります。ただし、コンパイル自体は時間がかかる場合があり、また、生成されたバイナリは特定のタイプのハードウェアやオペレーティングシステムでしか実行できないのが一般的です。CやC++などの言語は典型的にコンパイラを使用します。

【Compile】には翻訳という意味があります。本1冊を一気に翻訳するイメージです。

インタプリタ

一方、インタプリタはソースコードを一行ずつ読み込み、それを直接実行します。プログラムはすぐに実行されますが、全体としての実行速度はコンパイラに比べて遅くなる可能性があります。ただし、インタプリタを使用する言語は通常、プラットフォーム間の移植性が高く、開発が迅速であることが特徴です。PythonやRubyなどの言語はインタプリタを使用します。

【Interpret】には通訳という意味があります。会話をその都度通訳するイメージです。

なお、このコンパイラとインタプリタのアプローチは明確に分離されたものではなく、多くの現代の言語は「Just-In-Time(JIT)コンパイル」などのテクニックを用いて、コンパイラとインタプリタのいいとこ取りをしています。そうすることで動的な実行と高速なパフォーマンスの両方を実現しています。例えば、JavaやJavaScriptはこのようなアプローチを採用しています。

Javaの場合

Javaの場合はコンパイラとインタプリタの両方を使用する設計になっています。Javaのコンパイルとは、Java言語で書かれたソースコード(.java ファイル)をJavaバイトコード(.class ファイル)に変換するプロセスのことです。このバイトコードは、機械語ではなく中間的な形式であり、直接ハードウェア上で実行することはできません。Java Virtual Machine (JVM) が、生成されたバイトコードをインタプリタで実行します。JVMは、バイトコードを読み取り、それを対応するネイティブマシンコードに変換または解釈して実行します。そのため、JVMさえ入っていればどのようなOS上でも動作するのがJavaの強みです。

2. 基本的なプログラミング概念(変数、関数、ループなど)

基本的なプログラミング概念について説明します。例としてJavaを使っています。ぜひ、講師の指導のもと、Eclipseで実行してみてその動作を確認してみてください。

変数 【Variablesは、データを格納するための場所で、それぞれ一意の名前(識別子)がついています。Javaでは、変数を宣言するときにその型も指定する必要があります。以下に例を示します。

int myNumber = 5; // 'int'型の変数myNumberに5を代入
String myString = "Hello"; // 'String'型の変数myStringに"Hello"を代入

関数【Functionsは、特定のタスクを実行するためのコードのまとまりです。関数のことをJavaではメソッドと呼びます。

関数は引数を取り、結果を返すことができます。

void printHello() { // 引数なし、戻り値なしの関数
    System.out.println("Hello");
}

int addNumbers(int a, int b) { // 二つの整数を受け取り、それらの和を返す関数
    return a + b;
}

ループ (Loops)は、特定のコードブロックを繰り返し実行するための制御構造です。

主にforループとwhileループの2つの形があります。

for(int i = 0; i < 5; i++) { // iが0から4まで、5回繰り返すforループ
    System.out.println(i);
}

int i = 0;
while(i < 5) { // iが5未満である間、繰り返すwhileループ
    System.out.println(i);
    i++;
}

if文は、条件が真(true)の場合にコードブロックを実行します。

int num = 10;
if (num > 5) {
    System.out.println("Number is greater than 5");
}

else文は、if文の条件が偽(false)の場合にコードブロックを実行します。

int num = 10;
if (num > 15) {
    System.out.println("Number is greater than 15");
} else {
    System.out.println("Number is not greater than 15");
}

else if文は、前のif文またはelse if文の条件が偽(false)で、自身の条件が真(true)の場合にコードブロックを実行します。

int num = 10;
if (num > 15) {
    System.out.println("Number is greater than 15");
} else if (num > 5) {
    System.out.println("Number is greater than 5 but not greater than 15");
} else {
    System.out.println("Number is not greater than 5");
}

switch文は、変数の値によって異なるコードブロックを実行します。

int num = 3;
switch (num) {
    case 1:
        System.out.println("Number is 1");
        break;
    case 2:
        System.out.println("Number is 2");
        break;
    case 3:
        System.out.println("Number is 3");
        break;
    default:
        System.out.println("Number is not 1, 2, or 3");
}

配列は、同じ型の複数の値を一連の連続したメモリ領域に格納するデータ構造です。Javaの配列は固定長で、一度作成するとサイズを変更することはできません。

配列を宣言するには次のようにします:

int[] myArray;

新しい配列を作成し、その参照をmyArrayに代入するには次のようにします:

myArray = new int[5];

これで、5つの整数を格納できる配列が作成されました。初期化時にはすべての要素がその型のデフォルト値(intの場合は0)で埋められます。

配列の要素にはインデックスを使ってアクセスします。Javaの配列のインデックスは0から始まります。たとえば、配列の最初の要素にアクセスするには次のようにします:

int firstElement = myArray[0];

配列の要素を設定するには次のようにします:

myArray[0] = 100;

]

配列の長さ(つまり、配列に含まれる要素の数)を取得するには、配列名の後に .length をつけます:

int length = myArray.length;

また、配列の宣言と初期化を一度に行うこともできます:

int[] myArray = {1, 2, 3, 4, 5};

3. プログラミングパラダイム

プログラミングパラダイム【programming paradigm】とは、「プログラムというものをどのように見るか」という考え方です。つまり、プログラムを手続きの集まりと見るか、データの集まりと見るか、それとも、、、ということです。

プログラミングパラダイムの変遷は、プログラミング技術の進化と密接に関連しています。ここでは、手続き指向、データ指向、そしてオブジェクト指向という順番で、それぞれのパラダイムについて説明します。

  1. 最初のコンピュータプログラムは手続き指向と呼ばれるパラダイムに基づいていました。これはマシンレベルの指示に近く、一連の手続き(またはルーチン、手順)を順に実行していく方法です。手続きは入力を受け取り、出力を生成するための一連のステップを定義します。早い段階のプログラミング言語であるFortranやCOBOL、そしてC言語は、手続き指向プログラミングの典型例です。
  2. 次に来るのはデータ指向プログラミングで、ここではデータとそのデータに対する操作が中心となります。データはデータ構造(配列、リスト、セットなど)やデータベースに格納され、関数や手続きによって操作されます。データ指向プログラミングは、プログラムの構造を改善し、より大規模なシステムを扱うことを可能にしました。手続きに比べてデータは時間の経過とともに変化する度合いが少なく、安定的である点が利点です。
  3. オブジェクト指向プログラミングは、プログラムを相互に作用するオブジェクトの集合として表現するパラダイムです。各オブジェクトは、特定のデータ(プロパティや属性と呼ばれる)とそのデータを操作する方法(メソッドと呼ばれる)をカプセル化します。オブジェクト指向プログラミングは、コードの再利用性、拡張性を改善することに重点を置いており、JavaやC++、Pythonなど多くの現代的なプログラミング言語がこれに基づいています。

これらのパラダイムは必ずしも排他的なものではなく、多くのプログラミング言語はこれらの要素を組み合わせています。そして、各パラダイムは、特定の種類の問題を解決するためのツールと考えることができます。どのパラダイムを使用するかは、問題の性質、プログラマの経験、そしてプロジェクトの要件によって異なります。決して優劣ではないので誤解のないように。

話し合ってみましょう

図書館の業務をシステム化することを考えます。

  1. 手続き指向で考えると図書館はどのような手続きの集まりと考えられますか?
  2. データ指向で考えると図書館はどのようなデータの集まりと考えられますか?
  3. オブジェクト指向で考えると図書館はどのようなオブジェクトの集まりと考えられますか?

4. オブジェクト指向プログラミング(OOP)

オブジェクト指向プログラミング【Object-Oriented Programming : OOP】は、プログラミングのパラダイムの一つで、その設計は"オブジェクト"という概念を中心に据えています。このパラダイムは、コードの再利用性、部品化、効率的なコードの管理を促進することを目指しています。

オブジェクト指向プログラミングには以下の主要な要素があります。これまでの説明と重複する部分もありますが、

クラスはオブジェクトの設計図や雛形となります。クラスには属性(データ)とメソッド(そのデータに対する操作)が定義されています。例えば、「車」を表現するクラスを考えてみましょう。その属性は「色」や「メーカー」などで、メソッドは「運転する」や「停止する」などの操作になるでしょう。

新人エンジニア
Carクラス easyUML使用

インスタンスはクラスから具体化(インスタンス化)されたものです。オブジェクトはそのクラスが定義する属性とメソッドを持ちます。先程の「車」クラスから「私の車」を表現するオブジェクトを作れば、「色」属性には「赤」、「メーカー」属性には「トヨタ」など具体的な値が設定され、その車特有の状態と振る舞いを表現できます。(コードの詳細を今は意識しなくても結構です。以下同じ)

// 基本的な「車」クラス
public class Car {
	protected String color;
	protected String maker;

	public Car(String color, String maker) {
		this.color = color;
		this.maker = maker;
	}

	public void drive() {
		System.out.println("Driving the car.");
	}

	public void stop() {
		System.out.println("The car has stopped.");
	}
}

継承は新しいクラスが既存のクラスの属性やメソッドを引き継げる機能です。継承により既存のコードの再利用や、コードの一貫性を保つことが可能になります。例えば、「電気自動車」クラスは「車」クラスから属性とメソッドを継承し、その上に電気自動車特有の属性やメソッドを追加することができます。例えば、以下のElectricCarクラスの属性には色やメーカーといった属性がありませんが、このクラスはCarクラスを継承しているため色やメーカー属性を持っていることになります。

新人エンジニア
ElectricCarクラス easyUML使用
// 「電気自動車」クラス
public class ElectricCar extends Car {
	private int batteryLevel; // バッテリーレベル

	// コンストラクタ
	public ElectricCar(String color, String maker, int batteryLevel) {
		super(color, maker); // 親クラスのコンストラクタを呼び出す
		this.batteryLevel = batteryLevel;
	}

	// バッテリーレベルのゲッターメソッド
	public int getBatteryLevel() {
		return batteryLevel;
	}

	// バッテリーレベルのセッターメソッド
	public void setBatteryLevel(int batteryLevel) {
		this.batteryLevel = batteryLevel;
	}

	// 電気自動車特有のメソッド
	public void charge() {
		System.out.println("Charging the electric car.");
		this.batteryLevel = 100; // 例として、充電によりバッテリーレベルを100%にする
		System.out.println("Battery Level: " + batteryLevel + "%");
	}

	public static void main(String[] args) {
		ElectricCar tesla = new ElectricCar("Blue", "Tesla", 80);
		tesla.drive();
		tesla.stop();
		tesla.charge();
	}
}

ポリモーフィズムは、同じ名前のメソッドが異なる振る舞いをする能力です。コードの柔軟性が向上します。例えば、すべての「車」は「始動する」メソッドを持つかもしれませんが、その振る舞いは「ガソリン車」ではエンジンを始動するもので、「電気自動車」ではモーターを始動するものと、異なることができます。

カプセル化は、クラスの内部データや実装詳細を隠蔽し、その外部からの直接的なアクセスを制限する技術です。クラスの内部構造を自由に変更できるとともに、不適切な使い方からデータを保護することが可能になります。例えば、以下のElectricCarクラスにはバッテリーレベル属性がありますが、"-"マークで示されているようにクラス外からアクセスできないようになっています。

新人エンジニア
ポリモーフィズムの例 easyUML使用
//「ガソリン車」クラス
public class GasolineCar extends Car {

	public GasolineCar(String color, String maker) {
		super(color, maker);
	}

	@Override
	public void drive() {
		System.out.println("Starting the engine.");
	}
}

public class Main {
	public static void main(String[] args) {
		Car gasolineCar = new GasolineCar("Red", "Toyota");
		Car electricCar = new ElectricCar("Blue", "Tesla", 80);

		gasolineCar.drive(); // Outputs: Starting the engine.
		electricCar.drive(); // Outputs: Starting the motor.
	}
}

オブジェクト指向プログラミングは、JavaやC++、Pythonなど多くの現代のプログラミング言語で採用されています。このパラダイムを理解し実践することは、ソフトウェア開発の幅と深さを大いに広げることにつながります。

例題

基本情報処理技術者試験 平成21年秋期 午前問7 オブジェクト指向プログラムの問題にチャレンジしてみましょう。

次回は、「アルゴリズムを理解して効率の良いプログラムを作る」を学びます。

プログラミングの基本 最後までお読みいただきありがとうございます。