誰でも「それっぽく」使えるようになるクラス入門

2023/12/13に公開

<< 前:友達とBrainfuck命令セットCPUを作っている話 by chizuchizu
>> 後:Arduino Uno R4 WiFiを使ってLAN内から文章を表示してみた by kokastar

木更津高専 Advent Calendar 202314日目担当のこかすた〜です(公開日は少しだけ早いですが...。)
今回は、誰も簡単にそれっぽくクラスを使えるように、クラスの概念から説明していきます。

前置き

この記事は主に、同級生及び来年以降も同じ授業を受けるであろう後輩に向けて書いたものです。
学校のプログラミングの授業で普段はC言語を使っていますが、今回はゲーム制作ということでProcessingを用いることになっています。ProcessingはJavaベースですので、Javaのコードを交えながら解説していきます。

ところどころ厳密性がかけているところがあると思いますが、ご了承ください。厳密な説明を必要とする場合は、補足の章にて行っています。
また、説明をわかりやすくするために、用語の使用はなるべく避け、補足として用語を載せてあります。きちんと理解したい人は後ろの方にある用語の章に目を通すことをおすすめします。

この記事の対象

この記事は、プログラミング言語で関数などを扱える人を対象としています。解説言語はJavaですが、深い解説なしに使用する構文はCとほぼ同じですので、C言語等の経験があればJavaの経験がなくても問題なく読めると思います。
それ以外の言語の経験者であっても、多分問題なく読める程度にはかんたんな構文のみで作られています。

クラスってな〜に

クラスってどんなものなんでしょう。知ってる人はこの章まるごと飛ばしてください、時間は有限です。
よく言われるのは、「クラスは設計図で、それをもとに〜」みたいな説明です。でも、これはわかりにくいと僕は思います。

ということで、実際に例をあげながら説明していきます。例と言っても、ソースコードではなく、人間を例に考えてみます。

人間にもたせる情報や機能を考えてみる

人間っていろいろな情報や動きを持っていると思います。例えば、

  • 名前
  • PCの台数
  • 所持金(財布)
  • 銀行の残高(銀行口座)

などの情報を持っています。更に、

  • 名前を名乗る
  • 口座からお金を下ろす
  • PCを購入する

みたいな動作ができます。
人間の可能性は無限大なので他にもたくさんのことができますが、今回考える人間は上記の情報や動作を持つとしましょう。

ここで、「口座からお金を下ろす」「PCを購入する」という動作についてもう少しだけ考えてみます。この2つはそれぞれ、

  • 口座からお金を下ろす → 残高が十分にあれば銀行の残高を減らし、所持金を増やす
  • PCを購入する → 所持金が不足していて口座に十分お金があれば口座からお金を下ろし、PCの代金分所持金を減らしてPCの台数を増やす

と考えられます。

さて、ここまでの内容をまとめてみましょう。人間というのはこの記事においては以下のような情報や動作を持つものです。

種 類 名前 詳細
情報 名前
情報 PCの台数
情報 所持金(財布)
情報 銀行の残高(銀行口座)
動作 名前を名乗る 名前を聞かれたら名前を答える
動作 口座からお金を下ろす 残高が十分にあれば銀行の残高を減らし、所持金を増やす
動作 PCを購入する 所持金が不足していて口座に十分お金があれば口座からお金を下ろし、代金分所持金を減らして、PCの台数を増やす

このように、どのような情報(変数)や動作(関数)を持つかを決めたものを、プログラミング言語では「クラス」といいます。また、このクラスの情報を今やったように決めることを、「クラスの宣言」といいます。
このクラスのことを、Humanクラスと呼ぶことにしましょう。(ちなみに、クラスの名前は大文字から始めるのがプログラマの共通認識となっています)

ただし、これだけでは何もできません。ここからは、このHumanクラスを使って人を実際に創り出してみましょう。

Humanクラスを使ってみる

ここに、kokastarというHumanがいます。Humanということは、先程決めた情報や動作を持っていることになります。kokastarの情報を少し覗いてみましょう。

情報名 中身
名前 kokastar
PCの台数 3
所持金(財布) 1,700円
銀行の残高(銀行口座) 334,000円

では、早速動作を試してみましょう。kokastarに名乗らせるにはこうします。

kokastar.名前を名乗る()

すると、

kokastar

と名乗ってくれます。
次に、口座から1000円下ろしてもらいましょう。

kokastar.口座からお金を下ろす(1000)

情報を覗いてみると...

情報名 中身
名前 kokastar
PCの台数 3
所持金(財布) 2,700円
銀行の残高(銀行口座) 333,000円

確かに銀行の残高が減り、財布の中にお金が増えています。最後に、10万円のPCを購入してみましょう。

kokastar.PCを購入する(100000)

情報を覗いてみると...

情報名 中身
名前 kokastar
PCの台数 4
所持金(財布) 0円
銀行の残高(銀行口座) 235,700円

不足していた分を銀行口座から下ろしてPCを購入し、PCの台数が1台増えています。

クラスというのはこのように使えます。ただ単に情報を持つだけではなく、その情報を操作する動作なんかも一緒に用意することができます。

クラスの使いみち

クラスについて、何となくどのようなものかわかったかと思います。では、このクラスは具体的にどのようなところで使えるのでしょうか。

例えば、「ゲームのプレーヤー情報の管理」などの使えます。

種類 名前 詳細
情報 プレーヤー名
情報 X座標
情報 Y座標
情報 HP残量
情報 ランク
動作 ジャンプ 「y座標を+10する」×10のあとに「y座標を-10する」×10
動作 右に動く x座標を+10する
動作 左に動く x座標を-10する
動作 表示 今の座標に自身を表示させる
動作 非表示 自身を見えないようにする

このようなクラスを作成すれば、変数を直接変更することなく、

プレイヤー1.ジャンプ()

とするだけでジャンプできます。値をいじるよりも遥かにわかりやすくなると思います。
更に、変数を使って座標を管理すると「プレーヤー1のx座標」「プレーヤー1のy座標」などといくつも必要になりますが、クラスを使えば

プレイヤー1.X座標
プレイヤー1.Y座標

とかけます。

このように、クラスを使うことで「モノ」を扱いやすくできます。ゲーム制作においては様々なモノが出てくると思うので、それらをクラスを使って管理すると、扱いやすく読みやすいプログラムになると思います。

Java(processing)でクラスを使う

ここからは、実際にHumanクラスとkokastarというHumanをJava(processing)で実装してみましょう。

Humanクラスの宣言 - 情報

クラスにどのような情報を持てるようにするところから始めます。情報ということはつまり変数です。

Humanクラスの宣言はこうします。この{}の中身クラスの情報を書いていきます。
名前を変えたい場合は、Humanの場所を別の名前にで置き換えましょう。

class Human{

}

変数の宣言は、以下のように行います。
普通の変数とやってることは同じなので、難しくないと思います。C言語で構造体を知っている人であれば結構馴染みのある構文かもしれません。
注意点として、型の前にpublicというのを書いています。これの意味は補足の章で説明します。

class Human{
    public String name; //名前
    public int numOfpc; //PCの台数
    public int bankBalance; //銀行残高
    public int cash; //手持ちのお金
}

たったこれだけで、情報を持つための変数の作成ができました。

Humanクラスの宣言 - 動作

次は、動作の宣言をします。動作ということはつまり関数です。

早速、名乗るための関数であるprintName関数を書いてみましょう。これも型の前にpublicを書いていることに注意です。

class Human{
    public String name; //名前
    public int numOfpc; //PCの台数
    public int bankBalance; //銀行残高
    public int cash; //手持ちのお金
    
    public void printName(){ //名乗る
        println(name);
    }
}

println関数を使ってnameを表示します。このように、メソッドの中では変数を使えます。

また、引数も使えます(使い方ははこの後解説します)。口座からお金を下ろす関数は以下のように宣言できます。

class Human{
    public String name; //名前
    public int numOfpc; //PCの台数
    public int bankBalance; //銀行残高
    public int cash; //手持ちのお金
    
    public void printName(){ //名乗る
        println(name);
    }
    
    public void withdraw(int money){
        bankBalance -= money; //銀行からお金を減らす
        cash += money; //手元にお金を増やす
    }
}

引数で引き出す金額を入れると、そのぶん銀行からお金を減らし、手元にお金を増やします。

最後に、PCを購入する関数も作成してみます。

class Human{
    public String name; //名前
    public int numOfpc; //PCの台数
    public int bankBalance; //銀行残高
    public int cash; //手持ちのお金
    
    public void printName(){ //名乗る
        println(name);
    }
    
    public void withdraw(int money){
        bankBalance -= money; //銀行からお金を減らす
        cash += money; //手元にお金を増やす
    }
    
    public void buyPC(int price){
        if(cash>=price){ //もし手元の金額で足りるなら
            cash -= price;
            numOfpc += 1;
        }
        else if(cash + bankBalance >= price){ //総資産で足りるなら
                bankBalance -= price-cash; //手元のお金で足りない分引く
                cash = 0;
                numOfpc += 1;
        }
    }
}

まぁこんなところでしょう。この関数の内容はあまり重要ではないので、解説はコメントで書いておくに留めておきます。

これでクラスの宣言ができたので、次はこのクラスをもとにkokastarというHumanを作り出してみましょう。

kokastarというHumanを作り出す

ここから先はHumanクラスの宣言を毎回書くと埋まってしまうため、省略します。コードの前にはHumanクラスの宣言があると思って読んでください。

さて、kokastarの作り出し方ですが、至ってかんたんです。基本的な構文は以下のとおりです。

クラス名 作成するものの名前 = new クラス名();

よって、Humanクラスでkokastarを作りたい場合は以下のようにかけます。

Human kokastar = new Human();

これでkokastarが作り出せました。かんたんです。

kokastarを動かしたり情報を聞いたりする

kokastarに関数を実行させるには、kokastarの後ろに.関数名(引数)とすることで実行できます。
例えば、名乗らせたい場合は

Human kokastar = new Human();
kokastar.printName();

とすれば...

null

おや、おかしいです。実行結果がnullになりました。でも考えればそれはそうです。nameなどの情報をまだ決定していませんでした。
変数にアクセスしたい場合はkokastar.変数名でできます。そのため、すべての変数を設定してあげましょう。

Human kokastar = new Human();

kokastar.name = "kokastar";
kokastar.numOfpc = 3;
kokastar.bankBalance = 334000;
kokastar.cash = 1700;

kokastar.printName();

こうすれば実行結果は

kokastar

と問題なく名乗ってくれます。ただし、すべての変数において毎回代入するのは大変だと思います。それを解決する方法は後ほど紹介します。
他にも、銀行から1000円下ろしたければ

Human kokastar = new Human();

kokastar.name = "kokastar";
kokastar.numOfpc = 3;
kokastar.bankBalance = 334000;
kokastar.cash = 1700;

kokastar.withdraw(1000);
println(kokastar.bankBalance);
println(kokastar.cash);

と、通常の関数と同じように()の中に引数を入れた上で、変化を確認するためにprintln()で値を表示させると

333000
2700

と問題なく変化しています。PCの購入も自分でやってみてください。

一発でkokastarを初期化したい

先程はnameなどすべてを一つ一つ初期化していました。しかし、めんどくさいです。
ここでは、最初から値を入れる方法を2つ紹介します。

1.毎回同じ値を最初に代入する

今回はkokastar一人しか作成しませんでしたが、Humanはいくつでも作成できます。そのときに、毎回同じ値にしたいのであれば、クラスの宣言時に設定しておく方法があります。
例えば、すべてのHumanはPCを1台持っているのであれば、以下のようにかけます。

class Human{
    public String name;
    public int numOfpc=1;
    public int bankBalance;
    public int cash;
    
    //以下略
}

そうすると、最初からnumOfpc=1が代入された状態になります。
この方法は、例えばキャラクターの初期位置が(0,0)であると決まってる場合だとか、最初はHP満タンだからHP=100にしておくとか、そういう場合に使えます。

2.初期化時に設定したい

kokastarにはkokastarという独自の名前についているように、すべてのHumanが同じ値で初期化するのでは不都合なものもあります。その場合は、宣言時に初期化することができます。具体的には、

Human kokastar = new Human();

この段階で、new Human(ここに引数を入れる形で)値を設定できます。そのためにはまず、Humanクラスの中に以下の関数を加える必要があります。

クラス名(引数){
    引数を使った初期化処理
}

このとき、通常関数は返り値の型(なければvoid)を記述しますが、今回は返り値の方は書きません。
今回の場合は、name,numOfPc,bankBalance,cashの4つを初期化したいので、その初期化も含めたHumanクラスの宣言は以下のようになります。

class Human{
    public String name; //名前
    public int numOfpc; //PCの台数
    public int bankBalance; //銀行残高
    public int cash; //手持ちのお金
    
    public Human(String n,int num,int bank,int c){ 
        //変数名がかぶってしまうので、ここでは引数は頭文字だけとってます
        name = n;
        numOfpc = num;
        bankBalance = bank;
        cash = c;
    }
    
    public void printName(){ //名乗る
        println(name);
    }
    
    public void withdraw(int money){
        bankBalance -= money; //銀行からお金を減らす
        cash += money; //手元にお金を増やす
    }
    
    public void buyPC(int price){
        if(cash>=price){ //もし手元の金額で足りるなら
            cash -= price;
            numOfpc += 1;
        }
        else if(cash + bankBalance >= price){ //総資産で足りるなら
                bankBalance -= price-cash; //手元のお金で足りない分引く
                cash = 0;
                numOfpc += 1;
        }
    }
}

こうすることで、kokastarを作成するときに初期化できるようになります。あとは、関数同様に引数を指定すればいいので、

Human kokastar = new Human("kokastar",3,334000,1700);
Human kokasuta = new Human("kokasuta",3,334000,1700);
kokastar.printName();
kokasuta.printName();

とすれば、

kokastar
kokasuta

となります。このようにすることで、最初から初期の値を宣言できます。

終わりに

ここまでの内容を理解できたら、きっと多分クラスのこと「なんとなく」理解して使えるようになったと思います。
クラスを使うとプログラミングもより便利になると思います。この記事がその助けになれたら幸いです。

補足

ここでは、用語の解説や、扱ってないけど知ってると便利な機能など、補足を書きます。見なくてもプログラムはかけるので、余裕のある人だけで大丈夫です。

用語について

  • クラスの中で使用される変数のことを、Javaでは「フィールド」といいます
  • クラスの中で使用される関数のことを、Javaでは「メソッド」といいます
  • クラスによって生成されたもの(今回の場合kokastarなど)のことは、「インスタンス」といいます

インスタンスの生成について

インスタンスの生成について、記事では

クラス名 作成するものの名前 = new クラス名();

と書きましたが、正確には間違いです。正しくは、

クラス名 作成するものの名前 = new コンストラクタ名();

となります。コンストラクタとはクラスのインスタンス生成時に実行されるもので、名前はクラス名と同じになります。
ですから、クラスの定義に書いた

クラス名(引数){
    引数を使った初期化処理
}

は実はコンストラクタだったというわけです。
ちなみに、コンストラクタを作成しなかった場合は、空のコンストラクタが存在するとみなされてたりします。

アクセス修飾子について

フィールドやメソッドの宣言時に先頭にpublicを今までつけていたと思います。
これはアクセス修飾子と言って、そのフィールドやメソッドを参照できる(=実行したり、値を書き換えたりできる)範囲を設定します。不必要に広い範囲から変更可能にすると、意図せず値を書き換えてしまってバグが発生した場合、特定が難しくなります。そのようなことを防ぐため、値の変更できる範囲を制限できます。

アクセス修飾子には以下のような種類があります。

アクセス修飾子 範囲
public すべての場所からアクセス可能
protected 同じクラス内とそのサブクラスからアクセス可能
private 現在のクラス内でだけアクセス可能。

サブクラスとは、この下にある「クラスの継承」にて説明します。なるべく不要な変更を避けたい場合にはprivateにすることをおすすめします。
また、アクセス修飾子は省略することも可能です。省略したときの挙動はまた少し異なるので、各自で調べてみてください。

クラスの継承

※この章はかなり難易度が高めです!!

継承。カッコいい響きですね。
クラスの継承とは、既存のクラスをもとにその発展版のクラスを定義することです。例えば今回この記事ではHumanクラスを作りましたが、Humanクラスを継承して発展版であるPerfectHumanクラスを作成できます。
継承される側の大本のクラスのことを「スーパークラス」、継承された側のクラスのことを「サブクラス」といいます。継承は

class サブクラス名 extends スーパークラス名{

}

で行います。
今回は、Humanクラスを継承し、perfectLevelをreturnする機能を加えたPerfectHumanクラスを作ってみます。perfectLevelはPCの台数の10倍に比例するということにしましょう。

ここで一つ注意点があります。クラスの継承において、コンストラクタは継承されません。その代わりに、コンストラクタの1行目にsuper()を使うことで、スーパークラスのコンストラクタを実行することが可能です。引数も同様に扱えます。
このことを踏まえると、Humanクラスを継承したPerfectHumanクラスは以下のようになります。このようにすることで、Humanクラス同様インスタンス生成時に初期化が行えます。

class PerfectHuman extends Human{
    public PerfectHuman(String n,int num,int bank,int c){
        super(n,num,bank,c);
    }
}

次に、perfectLevelをreturnする機能を加えてみます。サブクラスからスーパークラスのフィールドやメソッドにアクセスするには、super.名前とします。そのため、

class PerfectHuman extends Human{
    public PerfectHuman(String n,int num,int bank,int c){
        super(n,num,bank,c);
    }
    public int perfectLevel(){
        return super.numOfpc * 10;
    }
}

PerfectHuman kokastar = new PerfectHuman("kokastar",3,334000,1700);
println(kokastar.perfectLevel());

で、perfectLevelを表示することができます。この場合は30になります。

クラスの継承というのは複雑であり、ここで紹介していない機能もたくさんあるので、ぜひ色々調べてみてください。

GitHubで編集を提案

Discussion