背景
現在、新卒でエンジニアになりオンボーディングを行っています。研究でC++を用いてプログラミングをしていて半泣きになりながらオブジェクト指向プログラミングについて理解した過去から、同期の理解の一助になればと勉強会を開いて理解のサポートをしました。そこで説明した内容をこちらでも供養できればと思います。
こちらの記事はクラスの概念をプログラミング初学者がざっくりとした理解をするを目的とした記事です。専門的で詳しい内容を知りたい方は別途自分で調べていくことをお勧めします。
私もJava初学者ですので、記事の内容で不備がある、わかりにくいなどあれば遠慮なくコメントなどいただけると嬉しいです。
目次
1.クラスとは
1-1.クラスのイメージ
クラスとは、繰り返し出てくるモノの設計図をあらかじめ作っておき、一つひとつをいちいち定義せずとも扱えるようにするものです。
わかりやすいところで言うと、ゲームのキャラクター(それぞれのキャラが攻撃力、防御力などの変数を持っている)などが例に挙げられることが多いです。
簡単なアプリを作っていたりすると、例えばメモ帳アプリだったらメモのタイトルと内容、社員情報だったらその人の名前と社員番号など、繰り返し同じ形のものが出てくるので、クラスを使います。(というかJavaやC++は基本すべてクラスを使ってプログラミングをします。)
1-2.クラスの中身を書きながら理解
ここでは社員情報を例にして理解していきましょう。クラスは基本的にメンバ変数とメンバ関数を持ち、インスタンス化(実体を作る)をすることによって、個々の社員の情報を保持することができます。
まずはクラスの定義を見てみましょう。
import java.util.*;
class Employee {
// メンバ変数の定義
int id;
String name;
}
クラスを定義した時点ではあくまでも設計図を作っただけなので、その設計図に従って実際の社員が入る入れ物(実体)を都度作ってあげないといけないんですね。その入れ物を作るときに使うのがコンストラクタという初期化のための関数です。さっきのクラスにコンストラクタを追加し、メイン関数で社員番号1番のTaroさんの実体を作成してみましょう。
import java.util.*;
class Employee {
// メンバ変数の定義
int id;
String name;
// コンストラクタの定義
Employee(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
// Employeeクラスの実体を作成
Employee employee = new Employee(1, "Taro");
}
}
コンストラクタはクラス名と同じ名前で設定します。コンストラクタの中身では、引数として受け取ったid
とname
をインスタンスのid
とname
へ格納するようになっていますね。そしてメイン関数でコンストラクタを呼び出し、Taroさんの実体を作成して、社員番号と名前を格納することができました!これだけではわかりにくいですが、複数人の社員の情報を格納したい場合でも、同様の手順で格納することができます!実例を見たい場合はこちらを見てみましょう。
では次に、社員の名前を返す関数であるgetName()
を定義し、社員の名前が正しく格納されているか確認してみましょう。
import java.util.*;
class Employee {
// メンバ変数の定義
int id;
String name;
// コンストラクタの定義
Employee(int id, String name) {
this.id = id;
this.name = name;
}
// 名前を返す関数
String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
// Employeeクラスの実体を作成
Employee employee = new Employee(1, "Taro");
// 名前を返す関数を呼び出し
String name = employee.getName();
// 名前を出力
System.out.println(name);
}
}
これでクラスに正しく格納されているかが見られると思います。paiza.ioなどを利用して実行してみてください。
2.例を見ながら解説
そんな訳でJavaを使っていくなら避けては通れないクラス。初心者には本当に理解しづらいですが、メンバ変数とメンバ関数については少し理解できた(と信じたい)と思います。そこで以下ではpaizaのクラスのメンバの更新 (paizaランク C 相当)の問題を題材にしてクラスの使い方と概念を解説していきます。
2-1.題材となる問題
(基本情報)
エンジニアであり、社員を管理する立場にあるあなたが勤める会社には、効率的に社員を管理するために、次のようなメンバ変数とメンバ関数を持つ社員クラスclass employee
が存在しています。
メンバ変数
整数 number, 文字列 name
メンバ関数
getnum(){
return number;
}
getname(){
return name;
}
(以降追加情報)
しかし、この社員クラスについて、社員番号や名前が変わった際にいちいち手動で更新するのは面倒だと感じたあなたは、引数に元の社員番号と新しい社員番号を指定すれば、新しい社員番号に更新してくれる関数 change_num
と 引数に元の名前と新しい名前を指定すれば、新しい名前に更新してくれる関数change_name
を作成することにしました。
入力でmake number name
と入力された場合は変数number, name
を持つ社員を作成し、getnum n
と入力された場合はn
番目に作成された社員のnumber
を、getname n
と入力された場合はn
番目に作成された社員のname
を出力してください。
また、change_num n newnum
と入力された場合は、n
番目に作成された社員のnumber
を、newnum
に変更し、change_name n newname
と入力された場合は、n
番目に作成された社員のname
を、newname
に変更してください。
2-2.問題を読んで
この問題はクラスを使って解いてね、と丁寧に導入された問題なので、それに従ってさっそくクラスを利用しましょう。ここで繰り返し出てくる要素の社員をまとめると以下のようになります。
社員
├ 変数
├ 社員番号
└ 名前
└ メソッド
├ 社員番号と名前を設定するメソッド(コンストラクタ)
├ 社員番号を返すメソッド
├ 名前を返すメソッド
├ 社員番号を変更するメソッド
└ 名前を変更するメソッド
基本情報より、このクラス(設計図)では、一人ひとりの社員に対して社員番号が入る箱と名前が入る箱を用意し、そこに情報を格納します。社員番号、名前を返すメソッド(getNum
、getName
)を追加します。
次に追加情報より、社員の番号、名前を変更するためのメソッド(changeNum
、changeName
)を追加します。
自分が書いたコードを以下に書きます。
2-3.自作コード
import java.util.*;
class Employee {
int id;
String name;
Employee(int id, String name) {
this.id = id;
this.name = name;
}
// 社員番号を返す
int getId() {
return id;
}
// 社員の名前を返す
String getName() {
return name;
}
// 社員番号を変更する
void changeId(int newId) {
this.id = newId;
}
// 社員の名前を変更する
void changeName(String newName) {
this.name = newName;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int total = sc.nextInt();
// 複数の社員(数はわからない)を格納するための可変長配列を定義
ArrayList<Employee> employees = new ArrayList<>();
int empNum, index;
String empName;
for(int i=0; i<total; i++) {
String command = sc.next();
// commandの中身によって分岐(switch文を使用)
switch(command) {
// もしmakeだったらコンストラクタを呼び出して可変長配列に格納
case "make":
empNum = sc.nextInt();
empName = sc.next();
employees.add(new Employee(empNum, empName));
break;
// もしgetnumだったらインデックスを取得して番号を出力
case "getnum":
index = sc.nextInt();
System.out.println(employees.get(index-1).getId());
break;
// もしgetnameだったらインデックスを取得して名前を出力
case "getname":
index = sc.nextInt();
System.out.println(employees.get(index-1).getName());
break;
// もしchange_numだったらインデックスと新しい番号を取得して番号を変更
case "change_num":
index = sc.nextInt();
empNum = sc.nextInt();
employees.get(index-1).changeId(empNum);
break;
// もしchange_nameだったらインデックスと新しい名前を取得して名前を変更
case "change_name":
index = sc.nextInt();
empName = sc.next();
employees.get(index-1).changeName(empName);
break;
default:
break;
}
}
}
}
いや、こんなの最初どうやって思いつくねん。初学者の人はそう思いますよね。個人的にはメイン関数から書いて、全体の流れをつかむことから始めるのがおすすめです。このメイン関数で意図していることは以下の通りです。
- 命令入力の数を格納
- 社員が入る箱である可変長配列をつくる
- 1.で格納した命令の数の分繰り返し、命令を格納する
- 命令の種類によって分岐する
この流れがつかめれば、あとは個々の命令を下すために必要な変数や関数が見えてくると思います。クラスの中身を書きながら理解の部分を見つつ、読み進めてみてください。
3.終わりに
クラスは難しい概念で最初はとてもとっつきにくいですが、なんとなく理解して問題を解いてみて意味わからなくて答えを見て...を繰り返していると、だんだんこういうこと...?という理解ができてくると思います。この記事を入り口としてクラスのジャングルをくぐり抜ける人がいてくれることを願います。