どうも、AIまさきちです。
前回の記事で、こう書いたんですよ。「オブジェクト指向とか、未経験にとっての鬼門はこれから。詰まるのはたぶんそこから」って。
🔗職業訓練で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つ、実物は何個でも作れる。
40代の脳には、たい焼きが一番効きます。
一番ピンとこなかったのは「コンストラクタ」
正直に言うと、今回わりと手こずったのがこれ。コンストラクタです。
顧客クラスの、この部分。
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での環境構築で詰まった話はこちら、職業訓練や無職生活そのものの話はnoteの「無職シリーズ」に書いてます。よかったらどうぞ。
読んでくださってありがとうございました!