Person クラスを作る

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

Person クラスを作る とは

Person クラスを設計しながら、フィールド・コンストラクタ・メソッドの基本を身につけよう。インスタンスから自己紹介の文字列を取り出す課題に挑戦する。

クラスは「設計図」、インスタンスは「実体」

ここまでのレッスンでは、intString のような すでに用意されている型 を組み合わせてプログラムを書いてきました。けれど現実のソフトウェアでは、「ユーザー」「商品」「予約」「投稿」のように、Java 標準にはない アプリ独自の概念 を表現したくなります。そんなとき登場するのが class です。クラスは「こんな情報とこんな振る舞いを持ったモノを作ります」という 設計図 で、その設計図から実際に作られた Java 上の物体を インスタンス (またはオブジェクト) と呼びます。

たい焼きの金型を思い浮かべてください。金型そのものは食べられない「設計図」、そこから焼き上がった一つひとつのたい焼きが「インスタンス」です。同じ金型から 太郎 のたい焼きも 花子 のたい焼きも焼けますが、それぞれは別の存在で、誰かが食べても他のたい焼きはそのまま残ります。

このレッスンでは、人を表す Person クラスを設計しながら、Solution.personIntroduction(String name, int age) という自己紹介の文字列を返すメソッドを完成させます。例として personIntroduction("太郎", 25) を呼ぶと "太郎 (25)" という文字列が返ってきます。たった 1 行の課題に見えますが、その裏には Java のオブジェクト指向のエッセンスが詰まっています。

Person クラスの最小形

まずは Person クラスの最小形を見てみましょう。name (名前) と age (年齢) という 2 つの情報を持つ人物を表現します。

Java

public class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } String introduce() { return this.name + " (" + this.age + ")"; } }

短いコードですが、Java のクラスを構成する 3 大要素である フィールド コンストラクタ メソッド がきれいに 1 つずつ入っています。順に読み解いていきましょう。

  • String name;int age; — フィールド (field)。クラスが内部で持っているデータ。インスタンスごとに別の値を持てる
  • Person(String name, int age) { ... } — コンストラクタ (constructor)。new Person("太郎", 25) のように呼んで、インスタンスを 生まれた瞬間 に初期化するための特別なメソッド
  • String introduce() — メソッド (method)。インスタンスに「やってもらう振る舞い」を書く場所

注目したいのは、コンストラクタとメソッドの中に出てくる this という言葉です。this.namethis は「いま動いているインスタンス自身」を指す Java の予約語で、引数の nameフィールドの name が同じ名前でぶつかったときに どちらの値を指したいかを区別するための合図 として使います。

this.name = name; は呪文ではなく、「いま動いている自分 (this) の name フィールドに、引数で受け取った name の値を入れる」という代入文です。左辺は フィールド 、右辺は 引数 、と頭の中で読み分けてください。慣れないうちは「内側 (this.name) に外側 (name) を入れる」と覚えると間違えにくくなります。

インスタンスを 2 つ作って独立性を体感する

クラスの威力は、同じ設計図から複数のインスタンスを作っても、それぞれが独立した状態を持てる ところにあります。試しに Person を 2 人ぶん作ってみましょう。

Java

Person taro = new Person("太郎", 25); Person hanako = new Person("花子", 30); String s1 = taro.introduce(); // "太郎 (25)" String s2 = hanako.introduce(); // "花子 (30)"

tarohanako は同じ Person クラスから作られていますが、taro.name"太郎"hanako.name"花子" で、まったく別のデータを保持しています。たい焼きの例えに戻ると、同じ金型から 太郎花子 の 2 匹のたい焼きが焼かれて、皿の上に並んでいる状態です。taro を食べても hanako には影響しません。

new キーワードはクラスからインスタンスを生み出すための合言葉です。new Person("太郎", 25) と書くと Java は内部で「Person クラスのインスタンス用にメモリ領域を確保 → コンストラクタを実行 → そのインスタンスへの参照を返す」という流れを実行してくれます。コンストラクタを呼ぶときは必ず new とセットだと覚えてください。

ここで、Person クラスと 2 つのインスタンスの関係を図にしておきます。設計図 (クラス) は 1 つ、そこから生まれたインスタンスは独立した別物、という関係をつかんでください。

diagram (will load when visible)

クラスは抽象的な「型」、インスタンスはメモリの上に実在する「モノ」です。Person が消えてもインスタンスは消えませんし、逆も同じです。

課題で書く Solution.personIntroduction の中身

このレッスンの課題では、Solution クラスの中に static メソッド として personIntroduction(String name, int age) を実装します。中身は実質、Person クラスを使った自己紹介と同じです。一番素直な書き方は、Person インスタンスを作って introduce() を呼ぶ流れをそのまま再現することです。

Java

public class Solution { public static String personIntroduction(String name, int age) { Person p = new Person(name, age); return p.introduce(); } static class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } String introduce() { return this.name + " (" + this.age + ")"; } } }

personIntroduction("太郎", 25) を呼ぶと、内部で new Person("太郎", 25) が走ってインスタンスが生まれ、その introduce()"太郎 (25)" を返してくれます。もちろん 1 行で return name + " (" + age + ")"; と書いても同じ結果になりますが、本レッスンの主役は クラスを実際に作って使う体験 です。テストが通る範囲で、ぜひクラスを定義して呼び出す形で書いてみてください。

static class Personstatic は、ここでは「外側の Solution クラスに紐づく内側クラス」を意味する書き方です。Java の入れ子クラス (nested class) と呼ばれます。本来 Person.java という別ファイルに分けるのが普通ですが、chotdekiru の Java Playground は単一ファイル前提なので、Solution の中に static class として書く形を採用しています。

クラスとデータ構造の使い分け

String 配列で [name, age] を表せばよくない?」と思った人は鋭いです。Java でも new String[] { "太郎", "25" } のように 2 要素の配列で持つことはできます。けれど、配列や Map のような汎用データ構造には次のような弱点があります。

  • 各要素が 何を表しているか がコードからは読み取れない (arr[0] が名前なのか年齢なのかが分からない)
  • 値の 型がそろってしまう ので、Stringint を混ぜたいときに不便 (Object[] にすると型安全性を失う)
  • 「自己紹介する」のような振る舞いを データと一緒に持つ ことができない

クラスを使うと、これらの弱点が一気に解消されます。Person pp.name p.age p.introduce() は、フィールドもメソッドも自分自身の中に閉じていて、コードを読んだだけで「これは人を表すモノだな」と分かります。

Java の世界には「データだけならクラスにしなくていい場面」もあります。たとえば短期的に値を一緒に運びたいだけなら、Java 16 で導入された record Person(String name, int age) {} という 1 行で済む書き方の方が向いています。record はフィールド・コンストラクタ・equalshashCode を自動生成してくれる、いわば「データを運ぶ用」の軽量クラスです。今回は class の基本を学ぶのが目的なので普通の class を使いますが、現場では classrecord を場面で使い分けます。

よくある間違い

初めてクラスを書くときに、ほとんどの人が一度はやらかすミスがあります。代表的な 3 つを押さえておきましょう。

  • フィールドの初期化漏れ — フィールドだけ宣言して、コンストラクタの中で this.name = name; を書き忘れるパターン。コンパイルは通ってしまうのに、introduce() を呼ぶと namenull のままで "null (0)" のような結果が返ってきます。Java では String のフィールドは初期値 nullint のフィールドは初期値 0 になる仕様なので、入れ忘れに気づきにくいのが厄介です
  • コンストラクタなしで放置する — コンストラクタを 1 つも書かないと、Java が自動で 引数なしのデフォルトコンストラクタ を作ってしまいます。すると new Person("太郎", 25) という書き方ができず、コンパイラから constructor Person in class Person cannot be applied to given types と怒られます。引数付きで初期化したいなら、その引数を取るコンストラクタを必ず自分で書く のが鉄則です
  • equals を実装していないのに == で比較する — 2 人の Person を比べたくて taro == hanako のように == で書くと、Javaメモリ上の同じ場所を指しているか だけを見ます。中身が同じでも new で別々に作っていれば結果は false です。中身を比べたいときは equals メソッドを自分で実装する必要があります

もうひとつ、地味ですが多いのが フィールドとローカル変数の混同 です。コンストラクタの中で this. を付け忘れて name = name; と書くと、引数の name を引数自身に代入しているだけで、フィールドの値は更新されません。Java のコンパイラは警告を出してくれることもありますが、確実にしたいなら this. を付ける ことを習慣にしてください。

エラーが起きたときの読み方のコツは、まず cannot find symbol cannot be applied to given types NullPointerException のような エラー名 を拾うことです。Java のエラーは長く見えますが、頭の単語だけ拾えば 8 割は原因の手がかりになります。

やってみよう

それでは右側のエディタで、Solution.personIntroduction(String name, int age) メソッドを完成させましょう。手順は次の通りです。

  1. メソッドのカッコの中が String name, int age の 2 つの引数になっていることを確認する
  2. Person インスタンスを new Person(name, age) で作る
  3. そのインスタンスの introduce() を呼んで、戻り値をそのまま return する
  4. 「実行」ボタンを押して、すべてのテストが緑になることを確認する

動いたら、ぜひ自由に実験してみてください。たとえば new Person(name, age) を 2 つ作って、それぞれの introduce() が独立した結果を返すことを確認してみたり、Person クラスに String greet() のような別のメソッドを追加してみたり、age の代わりに int birthYear を持たせて年齢を計算で求めるバージョンを書いてみたり、と発展課題はいくらでも作れます。

クラスを書けるようになると、Java で表現できる世界は一気に広がります。ユーザー、商品、注文、メッセージ、ゲームの登場人物 ─ どれも class の形でモデル化することで、データと振る舞いをひとつの塊として扱えるようになります。次のレッスン以降では、この Person クラスを土台に、クラス同士の関係や継承、record といった発展トピックに進んでいきます。今は「フィールド・コンストラクタ・メソッド」の 3 点セットを、自分の指でしっかり打ち込んで身体に覚えさせてください。

よくある質問

Q. クラスと関数はどう使い分けますか?

A. 状態(フィールド)を持たせて関連する処理をまとめたいときはクラス、純粋な計算だけなら関数で十分です。Java のように関数を単独で持てない言語ではクラス必須ですが、JS/Python はモジュール関数だけでも十分です。OOP の継承を活かすかどうかで判断しましょう。

Q. private と public の使い分けは?

A. フィールドは原則 private にして外から触らせず、getter / setter 経由でアクセスさせるのがカプセル化の基本です。public は呼び出し元に公開する API、private は内部実装の隠蔽。後から内部を変えても利用側が壊れないメリットがあります。

Q. 1 ファイルに複数クラスを書けますか?

A. Java は 1 つの public クラスにつき同じファイル名が必要です。public でない補助クラスは同じファイルに置けます。JavaScript/Python は何個でも置けますが、再利用性のために 1 ファイル 1 クラスを基本にした方が読み手に優しいです。

次のレッスン

次は Product クラスで税込価格 で、Person クラスを設計しながら、フィールド・コンストラクタ・メソッドの基本を身につけよう を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. Person の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. Person とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は personIntroduction、引数は String nameint age の 2 つにすること
  2. メソッドの戻り値の型は String で、"name (age)" の形式 (名前のあとに半角スペース、(、年齢、) を続ける) で返すこと
  3. Person 内部クラスを定義し、new Person(name, age) でインスタンスを作って introduce() を呼び出す流れで実装すること

入出力例

test-cases.txt

personIntroduction("太郎", 25)"太郎 (25)" personIntroduction("花子", 30)"花子 (30)" personIntroduction("Alice", 18)"Alice (18)" personIntroduction("赤ちゃん", 0)"赤ちゃん (0)" personIntroduction("長寿さん", 100)"長寿さん (100)"

ヒント

main.java
main.java
学習モード

メモ

Person クラスを作る

⌘S で保存