一般的なプログラミングの勉強はCでも十分ですが、オブジェクト指向プログラミングの思考を学ぶにはJavaがおすすめです。他にもオブジェクト指向の言語が色々ありますが、一番よく使われているのがJavaで、汎用性も高いです。基本文法はCと類似しているので、オブジェクト指向を中心としたサンプルを残しておきたいと思います。

このポストも随時更新していく予定です。

Hello World

プログラミングといえば、最初は'Hello World'ですね。

public class Main {
  public static void main(String[] args) {
    System.out.println("Hello World");
  }
}
Hello World

Cより複雑そうに見えますが、統合開発環境(IDE)を使えば、mainメソッドを自動生成してくれますので、覚えるのはSystem.out.printlnだけで十分です。

配列

Javaでは複数のデータを扱う際に、色々なオブジェクトを色々な方法で管理できるコレクションを良く使いますが、まずは基本的な配列と繰返し文の使い方を紹介したいと思います。次は奇数と偶数の合計を出力するサンプルです。

public class Main {
  public static void main(String[] args) {
    int[] numbers = {1, 4, 6, 9, 13, 16}; // 配列の宣言と初期化
    
    int oddSum = 0;
    int evenSum = 0;
 
    for (int i = 0; i < numbers.length; i++) { // 一般的なfor文
      if (numbers[i] % 2 != 0) { // 余りが0ではない場合
        oddSum += numbers[i];
      }
   }

    for (int number : numbers) { // 拡張for(for-each)文
      if (number % 2 == 0) { // 余りが0の場合
        evenSum += number;
      }
    }
    
   System.out.println("Odd Number Sum : " + oddSum); // 奇数の合計
   System.out.println("Even Number Sum : " + evenSum); // 偶数の合計
   }
}
Odd Number Sum : 23
Even Number Sum : 26

上記のサンプルでは配列を宣言と同時に初期化していますが、指定のサイズだけを確保したい場合は、int[] numbers = new int[6]のように書いてください。また、拡張for文は配列だけではなく、コレクションでも使えますので、覚えておくと便利です。

メソッド

メソッドはプログラミング言語でいう関数とほぼ変わりありませんが、オブジェクト指向のプログラミング言語においてはメソッドと定義しています。次は同じ名前のメソッドを複数定義できるオーバーロードを使った簡単なサンプルになります。

public class Main {
  public static void main(String[] args) {
    // ミドル・ネームを追加しない場合
    printData(fullName("Kafuu", "Chino"), 14, 1.44, 32.0);
    // ミドル・ネームを追加する場合
    printData(fullName("Hoto", "Bakery", "Cocoa"), 15, 1.54, 42.0);
  }
  
  public static void printData(String name, int age, double height, double weight) {
    System.out.println("Name : " + name);
    System.out.println("Age : " + age);
    System.out.println("Height : " + height + "m");
    System.out.println("Weight : " + weight + "kg");
  }
  
  // ミドル・ネームを入れない時に呼ばれるメソッド
  public static String fullName(String firstName, String lastName) {    
    return firstName + " " + lastName;
  }
  
  // ミドル・ネームを入れた時に呼ばれるメソッド
  public static String fullName(String firstName, String middleName, String lastName) {    
    return firstName + " " + middleName + " " + lastName;
  }
}
Name : Kafuu Chino
Age : 14
Height : 1.44m
Weight : 32.0kg
Name : Hoto Bakery Cocoa
Age : 15
Height : 1.54m
Weight : 42.0kg

上記のように、それぞれ引数の数やタイプが違うが、同じ名前のメソッドを複数定義することをオーバーロードと言います。もちろん違う戻り値を指定しても問題ありません。便利なオーバーロードですが、無闇に使うと後でわけが分からなくなったり、望まない結果を招く場合もあるので、濫用しないように注意が必要です。

継承

ここからは複数のクラスを扱うのでサンプルが長くなります。簡単なサンプルですが、オブジェクト指向を理解するために出来るだけクラスを分けました。まずは継承について説明します。例えば車を作る際に、機種が違う車でも車である以上、基本的な設計は同じです。この共有設計を共有するのを、継承とも言えます。Javaにおいては共通設計を「スーパークラス」、固有設計を「サブクラス」に例えられます。

public class Main {
  public static void main(String[] args) {
    // 新しい従業員のインスタンスを作成
    Employee cocoa = new Employee("Cocoa");

    // 新しい人形のインスタンスを作成
    Doll doll = new Doll("Magician Usagi");
    doll.setDesigner("Chino"); // 人形のデザイナーを設定
    
    cocoa.collection(doll); // ココアのコレクションを追加

    doll.getOwner().printName(); // 人形の持ち主を出力
    doll.printDesigner(); // 人形のデザイナーを出力
  }
}

public class Employee {
  private String name;

  // コンストラクタ
  Employee(String name) {
    this.name = name;
  }
  // アイテムの持ち主を設定するメソッド
  public void collection(Item item) {
    item.setOwner(this);
  }
  public void printName() {
    System.out.println(this.name);
  }
}

// 抽象メソッドがあるため、抽象クラスになる
abstract class Item {
  private String name;
  protected String designer = "Rize";
  private Employee owner;

  // コンストラクタ
  Item(String name) {
    this.name = name;
  }
  public String getName() {
    return this.name;
  }
  public Employee getOwner() {
    return this.owner;
  }
  public void setName(String name) {
    this.name = name;
  }
  public void setOwner(Employee employee) {
    this.owner = employee;
  }
  public void printDesigner() {
    System.out.println(this.designer);
  }
  
  // 抽象メソッドの定義
  abstract public void setDesigner(String designer);
}

// アイテムクラスを継承する
public class Doll extends Item {
  Doll(String name) {
    super(name); // スーパークラスのコンストラクタを呼び出す
  }
  public void setDesigner(String designer) {
    this.designer = designer;
  }
}
Cocoa
Chino

単に人形の持ち主とデザイナーを出力するだけのプログラムですが、できるだけオブジェクト指向のプログラムにしてみました。ここで全てを説明することはできないので、簡単にプログラムの流れをまとめると次のようになります。

  1. 従業員(Cocoa)を定義する。
  2. Magician Usagiという人形を作成する。
  3. 人形はItemという共通部分を共有(継承)する。
  4. 人形固有のデザイナーを設定する。
  5. 従業員(Cocoa)のコレクションに人形を追加する。
  6. 共通部品(Item)から人形の持ち主を出力する。
  7. 共通部品(Item)から人形のデザイナーを出力する。

一般的なテキストの例として使われる車(car)に比べると、あまりいい例ではありませんが、現実世界のように従業員や人形などをオブジェクト化していることを注目してください。abstractsuperなど、他にも色々な概念が含まれていますが、このページでは割愛させて頂きます。

インタフェース

インタフェースは前項でも触れました継承と抽象クラスと似た概念です。ただ、インタフェースを宣言したクラスには抽象メソッドのみの定義が可能です。変数は暗黙的にpublic static finalの修飾子が付くので定数となります。実装の際はextendsではなく、implementsを使います。また、複数実装が可能な特徴を持っています。

public class Main {
  public static void main(String[] args) {
    // インタフェース「RabbitHouse」型としてインスタンスを作成
    RabbitHouse chino = new Chino();
    RabbitHouse cocoa = new Cocoa();

    RabbitHouse[] employee = { chino, cocoa };

    for (RabbitHouse emp : employee) {
      emp.introduce();
    }
  }
}

// 複数のインタフェースをインプリメントする
public class Chino implements Coffee, Order {
  public void canOrder() {
    System.out.println(Order.description);
  }
  public void makeCoffee() {
    System.out.println(Coffee.description);
  }
  public void introduce() {
    System.out.println("チノです。");
    canOrder();
    makeCoffee();
  }
}

// 複数のインタフェースをインプリメントする
public class Cocoa implements Bake, Order {
  public void canOrder() {
    System.out.println(Order.description);
  }
  public void canBake() {
    System.out.println(Bake.description);
  }
  public void introduce() {
    System.out.println("ココアです。");
    canOrder();
    canBake();
  }
}

// 型を一つにまとめるための親インタフェース
public interface RabbitHouse {
  public void introduce();
}

// インタフェース「RabbitHouse」を継承する
public interface Order extends RabbitHouse {
  public static String description = "接客ができます。";

  void canOrder();
}

// インタフェース「RabbitHouse」を継承する
public interface Coffee extends RabbitHouse {
  public static String description = "コーヒーを淹れられます。";

  void makeCoffee();
}

// インタフェース「RabbitHouse」を継承する
public interface Bake extends RabbitHouse {
  public static String description = "パンが焼けます。";

  void canBake();
}
チノです。
接客ができます。
コーヒーを淹れられます。
ココアです。
接客ができます。
パンが焼けます。

単純なインタフェースだけではなく、ポリモーフィズムの観点も含めて少し長くなりましたが、プログラムはすごく簡単です。ここで一番注目して欲しい部分はMainクラスのRabbitHouse[] employee = { chino, cocoa };です。ChinoとCocoaはそれぞれ違うクラスですが、RabbitHouseを継承しているインタフェースをインプリメントすることで、同じ型として扱えるようになりました。これがポリモーフィズムです。

プログラム自体は単純にインプリメントしたインタフェースの抽象メソッドを実装し、呼び出しているだけですので詳細の説明は割愛させて頂きます。

例外

例外とは簡単にいえば想定外のエラーのようなもので、一見問題なく動いてるプログラムでも、パラメータが空だったり、特殊文字が入ったりすることで、エラーが起こるケースがしばしばとあります。Javaでは例外クラスより、エラーの発生時に自動で例外処理が走りますが、直接例外をキャッチすることもできます。

public class Main {
  public static void main(String[] args) {
    RabbitHouse rabbitHouse = new RabbitHouse();
    rabbitHouse.welcome();
  }
}

public class RabbitHouse {
  // 三番目に呼ばれるメソッド
  private void callEmp() throws Exception {
    System.out.println("Call Employee");
    String[] emp = { "Chino", "Rize", "Cocoa" };
    // emp[3]はnullのためエラーとなる
    System.out.println(emp[3]);
  }

  // 二番目に呼ばれるメソッド
  private void order() {
    System.out.println("Order");
    try {
      callEmp();
      // callEmp()で発生した例外をキャッチする
    } catch (Exception e) {
      System.out.println("Missing Employee");
      e.printStackTrace();
      return;
    }
    System.out.println("May I help you?");
  }

  // 一番目に呼ばれるメソッド
  public void welcome() {
    System.out.println("Welcome");
    order();
    System.out.println("Good Bye");
  }
}
Welcome
Order
Call Employee
Missing Employee
java.lang.ArrayIndexOutOfBoundsException: 3
Good Bye

callEmpメソッドに付けているthrows Exceptionは、該当メソッドで例外が発生した場合、呼び出し元へと例外ハンドラを探しに行くことを意味します。因みに、Exceptionクラスはあらゆる例外クラスの親クラスで、大概の例外はこれでキャッチできると思います。上記のサンプルではcallEmpメソッドで起こった不正インデックス参照例外を、orderメソッドでキャッチしています。正常な配列インデックスを使って、正常終了の動作も確認してみてください。

スレッド

スレッドとは1つのプログラム上で複数の処理が同時に実行されることを言います。つまり、マルチタスクのことで、私達が使ってるWindowsやインターネットブラウザも同じです。人間の目にはまるで同時に実行されているように見えますが、実は早いスピートで処理を切り替えているわけです。次は競走をテーマとして、二つのインスタンスを同時に実行するサンプルになります。

public class Main {
  public static void main(String[] args) {
    Race player1 = new Race();
    Race player2 = new Race();

    Thread chino = new Thread(player1, "Chino");
    Thread cocoa = new Thread(player2, "Cocoa");

    chino.start();
    cocoa.start();
  }
}

public class Race implements Runnable {
  private final int GOAL = 100;
  protected int progress = 0;
  Random random = new Random();

  public void run() {
    StringBuffer sb = new StringBuffer();
    String name = Thread.currentThread().getName();

    while (true) {
      // ランダムで0〜30の数字が足されていく
      progress = progress + random.nextInt(30);
      sb.delete(0, sb.toString().length());

      // 進捗が100以上の場合、ゴールとする
      if (progress >= GOAL) {
        progress = 100;
        System.out.println(name + " : Goal!");
        break;
      // 進捗が100未満の場合、処理を継続する
      } else {
        for (int i = 0; i < progress; i += 5) {
          sb.append("*");
        }
        System.out.println(name + " : " + sb);
      }
      try {
        // 0.2秒間処理を一時停止する
        Thread.sleep(200);
      } catch (Exception e) {
      }
    }
  }
}
Chino : ****
Cocoa : ******
Chino : ****
Cocoa : ********
Chino : ********
Cocoa : *************
Chino : ************
Cocoa : ****************
Cocoa : *****************
Chino : *****************
Chino : Goal!
Cocoa : *******************
Cocoa : Goal!

スレッドを作成するには、Threadクラスを継承するか、Runnableインタフェースを実装する必要があります。但し、Javaでは2つ以上のクラスを継承することはできないため、Runnableインタフェースを実装する方法を選ぶ場合が多いです。どちらの方法もrunメソッドをオーバーライドして自由に処理を記述します。そして、そのクラスのインスタンスを使ってThreadクラスのインスタンスを作成し、startメソッドでスレッドを実行することができます。

コレクション

コレクションとは色々な要素に集まりのことで、言葉通りの意味を持っています。複数のデータを扱うには配列を使う方法もありますが、コレクションを使えばデータの順序を簡単に変えられるとか、後でデータを簡単に追加もしくは削除できるなどのメリットがあります。コレクションにはListMapSetなどのインタフェースがありますが、内容が膨大になるため、今回はデータ管理にハッシュを使う、ちょっと特殊なHashSetクラスを紹介したいと思います。

public class Main {
  public static void main(String[] args) {
    HashSet<Employee> hashSet = new HashSet<Employee>();
    // Employeeクラスのインスタンスを追加
    hashSet.add(new Employee("Chino", 14));
    hashSet.add(new Employee("Cocoa", 15));
    hashSet.add(new Employee("Rize", 16));
    System.out.println(hashSet.toString());

    // 既に作成したインスタンスと同じ中身のインスタンスを作成
    Employee employee = new Employee("Cocoa", 15);
    // 同じ中身のインスタンスをhashSetより削除
    hashSet.remove(employee);
    System.out.println(hashSet.toString());
  }
}

public class Employee {
  private String name;
  private int age;

  public Employee(String name, int age) {
    this.name = name;
    this.age = age;
  }

  // toStringをオーバーライド
  public String toString() {
    return name + "(" + age + ")";
  }

  // equalsをオーバーライド
  public boolean equals(Object obj) {
    String compareValue = obj.toString();
    String thisValue = toString();
    return thisValue.equals(compareValue);
  }

  // hashCodeをオーバーライド
  public int hashCode() {
    return toString().hashCode();
  }
}
[Chino(14), Cocoa(15), Rize(16)]
[Chino(14), Rize(16)]

HashSetクラスはハッシュアルゴリズムを使うため、格納順にデータが並びません。上記のサンプルはあいにく順番通りに表示されていますが、保証はされません。順番を持たないため、格納されたデータを削除するには、removeメソッドに削除したいデータと同じデータを渡す必要がありますが、Employeeクラスの中身、つまり名前と年齢を持って同じデータであることを確認させるには、equalsメソッドとhashCodeメソッドをオーバーライドして書き直す必要があります。equalsメソッドについては以下の説明が役に立つと思います。

==演算子とequalsメソッドの違い

上記のサンプルではequalsメソッドでの判定を、EmployeeクラスのtoStringメソッドの結果が同じであるかを見て下しています。また、hashCodeメソッドも同じく、toStringメソッドの結果を使っています。内容が複雑になってしまいましたが、とにかくHashSetクラスを使いたい場合は、equalsメソッドとhashCodeメソッドを実装する必要があることを意識してください。

オブジェクトのソート

コレクションフレームワークのComparatorを使えばオブジェクトのソートが簡単にできるようになります。Comparatorには色々なメソッドが用意されていますが、今回はcomparingメソッドをメインに、Listの中にある要素の並び順を変えてみたいと思います。並び順を変えるためのキーは自作したEmployeeクラスのgetメソッドから参照しています。(クラス名::メソッド名

public class Main {
  public static void main(String[] args) {
    List<Employee> employees = new ArrayList<Employee>();

    employees.add(new Employee("Chino", 14, 4, 5));
    employees.add(new Employee("Cocoa", 15, 1, 4));
    employees.add(new Employee("Rize ", 16, 2, 3));
    employees.add(new Employee("Sharo", 15, 5, 7));
    employees.add(new Employee("Chiya", 15, 5, 5));

    System.out.println("-- Before --");
    showEmp(employees);

    // Age(年齢)とYear(経歴)は降順、Hour(勤務時間)は昇順
    Comparator<Employee> comparator = Comparator.comparing(Employee::getAge).
        thenComparing(Employee::getYear).reversed().thenComparing(Employee::getHour);
    employees = employees.stream().sorted(comparator).collect(Collectors.toList());

    System.out.println("-- After --");
    showEmp(employees);
  }

  public static void showEmp(List<Employee> employees) {
    for(Employee emp : employees) {
      System.out.println(emp.toString());
    }
  }
}

Employeeクラスは内容が長くなるため省略しました。Listの使い方も前項のコレクションで紹介したHashSetと似ているため省略しました。注意するポイントは並び順を降順にするためのreversedと複数キーを指定するためのthenComparingですね。Comparatorの作成が終わったら、Listのインスタンスからstreamを呼び出し、作成したComparatorでソートをします。使い方はサンプルのソースをご参考ください。