【つまずき集】40代未経験がJavaのオブジェクト指向でガチで詰まった話(クラス・コンストラクタ・継承)

前回の記事で、「オブジェクト指向など、未経験にとっての鬼門はこれから。詰まるのはたぶんそこから」と書きました。
🔗職業訓練でJavaを学ぶ40代が、Claude Codeを“専属の家庭教師”にしてる話
予告どおり、詰まりました。
この記事では、職業訓練でオブジェクト指向に入った40代未経験が、実際にどこで分からなくなったかを具体的に書いていきます。同じところで詰まっている人の参考になれば嬉しいです。
授業は引き続きZoomで1日6コマ、開発ツールはEclipseです。Macで環境構築したときの話はこちらの記事にまとめています。
クラスを書いても、何も起きない
最初に詰まったのはここでした。
授業で最初に書いたのが、この商品クラスです(クラス=プログラムの“設計図”のようなもの)。
public class Item {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public void showInfo() {
System.out.println("商品名:" + name);
System.out.println("値段:" + price + "円");
}
}
見よう見まねで書くこと自体はできます。ただ、これだけだと何も動きません。
書き終えて最初に思ったのは、「これはどうやって実行するのか」でした。
答えは、new Item("りんご", 100) と書いて初めて“実物”(インスタンス)ができる、です。ただ、この「クラス=設計図」という説明が、最初はまったくピンときませんでした。
詰まったのでClaude Codeに聞いてみたところ、こんな例えが返ってきました。
クラスは「たい焼きの型」。newは「その型でたい焼きを1個焼く」こと。型をいくら眺めても、お腹は膨れない。
これで腑に落ちました。Itemクラスは型で、new Item("りんご", 100) と new Item("みかん", 80) は別々に焼いたたい焼きです。型は1つ、実物は何個でも作れる。
教科書的な定義より、この手の具体的な例えのほうが、はるかに頭に入りました。
一番ピンとこなかったのは「コンストラクタ」
今回わりと手こずったのが、コンストラクタです。
顧客クラスの、この部分です。
public class Customer {
protected int deposit; // 預り金
public Customer(int deposit) { // ← これがコンストラクタ
this.deposit = deposit;
}
}
クラス名と同じ名前の、この Customer(...) という部分です。メソッドのように見えるのに、void のような戻り値の書き方がありません。「これは何なのか。いつ動くのか」と、しばらくモヤモヤしていました。
ここが腑に落ちるまでが、一番時間がかかったかもしれません。
答えは、**new した瞬間に自動で1回だけ動く“最初の準備係”**です。new Customer(1000) と書くと、この中身が走って、預り金に1000をセットしてくれます。自分でわざわざ呼び出すのではなく、newに“くっついて”自動で動く。ここが分かっていませんでした。
その準備係の中にある、この1行も引っかかりました。
this.deposit = deposit;
左と右で、同じ名前が並んでいます。 これも最初は意味が分かりませんでした。種明かしをすると別物で、
- 右の
deposit= 外から受け取った引数(渡されてきた値) - 左の
this.deposit= クラス自身が持っている変数(thisは「自分自身」の意味)
つまり「外から受け取った預り金を、自分のポケットにしまう」という1行です。コンストラクタの役割(=newした時の最初の準備)が分かると、この1行が何のためにあるのかも、一緒に腑に落ちました。
同じpurchaseが2つある。どっちが動くの?(継承・extends)
今回いちばんの山がここでした。**継承(extends)**です。
授業の課題で、普通の顧客(Customer)と、特別会員(SpecialCustomer)を作ることになりました。特別会員は商品を25%オフで買える、という設定です。
public class SpecialCustomer extends Customer {
private final double SPECIAL_RATE = 0.25;
public SpecialCustomer(int deposit) {
super(deposit);
this.deposit = (int)(deposit * (1 + SPECIAL_RATE));
}
@Override
public void purchase(int price) {
int specialPrice = (int)(price * (1 - SPECIAL_RATE));
deposit -= specialPrice;
System.out.println(price + "円の商品を特別価格" + specialPrice + "円で購入");
}
}
まず自分で書いてみて、動くには動きました。ただ、頭の中は疑問だらけでした。
extends Customerとは何なのかsuper(deposit)は誰を呼んでいるのか- 親にも
purchaseがあるのに、子にもpurchaseがある。どちらが動くのか
特に最後の疑問です。同じ名前のメソッドが2箇所にあるのが、どうにも気持ち悪く感じました。
例によって自分でしばらく悩んでから、Claude Codeにコードを見せて聞きました。返ってきた説明をまとめると、こうです。
extends Customer= 親(Customer)の中身をまるごと引き継ぐ。預り金の変数などを書き直さなくていいsuper(deposit)= 親のコンストラクタ(最初の準備処理)を呼ぶ。「親の初期設定を先にやっておいて」という合図@Override= 親のやり方を上書きして自分流にする宣言。SpecialCustomerで買い物すると、上書きした方(25%オフの方)が動く
継承は「親のコピーを作る」んじゃなくて、「親を土台にして、違うところだけ書く」仕組み。
これが分かった瞬間、SpecialCustomer のファイルが妙に短い理由も腑に落ちました。親にある機能は書かなくていいので、短くなるという理屈です。
ちなみに (int)(price * (1 - SPECIAL_RATE)) のような型が混ざる計算も地味な罠です。「int同士の割り算で小数が消える」系の話は前回の記事で書いたので、そちらをどうぞ。
息抜き:二次元配列は「Excelの表」だった
最後に、オブジェクト指向の少し前にやった二次元配列の話も軽く書いておきます。ジャンケンの勝敗判定で、こういうコードを書きました。
// 勝敗表 0:引き分け 1:勝ち -1:負け(縦:自分の手 / 横:コンピュータの手)
int[][] table = {
{ 0, 1, -1 },
{ -1, 0, 1 },
{ 1, -1, 0 },
};
int result = table[playerHand][computerHand];
最初は [縦][横] の添字で目が滑りました。ただ、図で説明されて「Excelの表と同じ」と気づいてからは一発でした。縦に自分の手、横に相手の手を並べた勝敗表から、1マス取り出しているだけです。
「表のどのマスか」というイメージさえ掴めれば、ここは意外とすんなり理解できました。
詰まったら「まず自分で悩む→それからAIに聞く」
以上が、オブジェクト指向の入り口で詰まったポイントです。
振り返ると、「超巨大な壁」というほどではありませんでした。ただ、未経験には十分キツいです。特に継承のあたりは、1回書いただけでは消化しきれないと思います。
私のやり方は前回から変わらず、この順番です。
- まず自分で書いて、自分で悩む
- それでも分からなかったら、Claude Codeに「答え」ではなく「説明」を聞く
もうひとつ、今回強く実感したのが、授業中に、その場で聞けるありがたさです。Zoomの授業で分からない箇所が出た瞬間、隣でClaude Codeにこっそり聞けます。あとで家に帰って復習するのと比べて、その場での理解の入り方が段違いです。分からないモヤモヤを抱えたまま次に進まずに済むのが、地味に大きい利点でした。
訓練校の方針も「生成AIはOK、ただしまず自分で考えること」なので、ちょうどいいバランスだと思っています。
ポリモーフィズムやインターフェースなど、鬼門はまだ続くようなので、また詰まったら書きます。
Macでの環境構築で詰まった話はこちらに書いています。あわせてどうぞ。