読み込み中...

Javaクラス継承の完全解説!初心者でも理解できる10ステップ

初心者が学ぶJavaのクラスと継承の完全ガイド Java
この記事は約29分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaプログラミングでクラスと継承を押さえると、値と処理をひとまとまりにした設計を読み解きやすくなります。その理解は、classextendsnewthissuperの役割をコード上で結び付けるところから始まります。プログラミング初心者がつまずきやすいのは、文法名を覚えても、どの行がオブジェクト指向のどの考え方に対応するかを見失う点になるのが基本です。

そのため、Javaのクラスは「状態と振る舞いの型」、継承は「共通部分を親クラスへ寄せる仕組み」と整理すると学習しやすくなると考えられます。Javaプログラミングの基礎を固めたい場合は、JavaのList型Javaのオーバーライドもあわせて確認すると、型と再利用の流れがつながります。

動作確認環境
  • Java SE 21 / JDK 21
  • 標準ライブラリのみ、コマンド例は javacjava を想定
📖 この記事で学べること
  • Javaのクラスが属性とメソッドをまとめる理由
  • 継承、オーバーライド、オーバーロードの違い
  • コンストラクタ、thissuperの使い分け
  • 抽象クラス、インターフェース、シングルトンの基礎
  • プログラミング初心者が読みやすいサンプルコードの見方

Javaとは

public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }

結果: 期待される出力は「Hello, World!」です。

Javaは、ソースコードをjavac.classファイルへコンパイルし、JVM上で動かすプログラミング言語です。この仕組みにより、同じJavaプログラミングのコードを複数の環境で扱いやすくなります。公式ドキュメントの仕様はJava Language Specificationで確認できます。

一般に、Javaはオブジェクト指向を中心に設計されており、Stringintdoublebooleanのような型を使い分けながら処理を組み立てますが、覚えておくと役立つでしょう。クラスを単位にした設計は、業務アプリケーション、Androidアプリ、サーバーサイド開発などで長く使われてきました。一方、プログラミング初心者にとっては、ファイル名、クラス名、mainメソッドの対応を最初にそろえることが理解の近道になります。

Javaの特徴

Javaの特徴は、オブジェクト指向、プラットフォーム非依存、型安全性、標準ライブラリの厚さに整理できます。その中でもクラスと継承は、Javaプログラミングの読み書きで繰り返し出てくる中心的な考え方になると言えるでしょう。ただし、言語の特徴を暗記するだけではコードの流れが見えにくいため、サンプルコードと対応させて覚えるとよいでしょう。

項目主な構文役割初心者が見る点関連する考え方
クラスclass属性とメソッドをまとめるファイル名とクラス名オブジェクト指向
インスタンス化newクラスから実体を作る変数に代入する流れ参照型
継承extends親クラスの要素を引き継ぐis-a関係再利用
オーバーライド@Override親メソッドを子クラスで置き換える引数と戻り値ポリモーフィズム
オーバーロード同名メソッド引数違いで処理を分けるシグネチャAPI設計
コンストラクタpublic Person()生成時に初期化する戻り値を書かない点初期状態
抽象クラスabstract共通処理と未実装処理を持つ直接生成できない点継承設計
インターフェースinterface実装すべき操作を示すimplements契約

オブジェクト指向

これにより、データを表すフィールドと、そのデータを扱うメソッドを同じクラスへ置けます。例えばnameageを持つ人物クラスでは、表示処理をintroduceとして近くに配置できます。オブジェクト指向は抽象的に見えますが、関連する値と処理を離しすぎないための整理法と考えると実用的です。

プラットフォーム非依存

その特徴は、Javaのソースコードがバイトコードへ変換され、JVMで動作する流れから生まれますし、ここを基本と考えるとよいでしょう。OSごとに完全に同じ挙動を保証するわけではありませんが、標準仕様に沿う範囲では移植性を高められます。Javaプログラミングでは、System.out.printlnのような標準APIから始めると環境差を意識しすぎずに学べますし、ここがポイントです。

セキュリティ

ただし、Javaを使えば自動的に安全になるわけではありません。アクセス修飾子のpublicprotectedprivateを適切に使い、外部から変更してよい範囲を狭める設計が必要になります。クラスの責務を小さく保つことも、意図しない変更を減らす考え方につながりますし、ここがポイントです。

用途の広さ

一方、JavaはWebアプリケーション、バッチ処理、デスクトップツール、組み込み寄りのシステムまで広い領域で使われます。用途が広いぶん、学習の入り口では標準構文に集中するほうが扱いやすいでしょう。Javaのエスケープ処理を押さえると、文字列出力やログの読み方も整理できます。

具体的には、文字列を扱うString、数値を扱うintdouble、真偽値を扱うbooleanを組み合わせながら、処理をメソッドへ分けますが、これは押さえたい点です。その分け方がクラス設計の出発点になり、継承やインターフェースの理解にもつながります。Javaの徹底解説として読む場合は、言語の特徴を単語で覚えるより、どの構文が再利用や変更しやすさに関係するかを意識するとよいでしょう。

Javaの歴史

Javaは1990年代にSun Microsystemsで開発され、のちにOracleが引き継ぎました。その経緯よりも学習上で押さえたいのは、古い書き方と現在の標準的な書き方が混在しやすい点です。公式のJDK 21 Documentationのような一次情報を基準にすると、古いAPIの説明へ引きずられにくくなります。

具体的には、クラス、継承、例外、コレクションといった基礎構文は長く使われていますが、リリースごとに言語機能や標準APIは増えているのが基本です。そのため、プログラミング初心者は最新機能を追いかける前に、classinterfaceenumpackageimportを読める状態にすると学習が安定します。Javaの徹底解説として見るなら、まず構文の役割をコード上の位置で判断する視点が役立ちますが、これは押さえたい点です。

Javaのクラスとは

Javaのクラスは、属性を表すフィールドと、操作を表すメソッドをまとめる設計単位です。オブジェクト指向では、クラスを設計図、newで作られる値をインスタンスとして扱います。クラスの名前は大文字で始める慣習があり、CarPersonのように役割が分かる名にするのが一般的です。

その構成を読むときは、クラス名、フィールド、コンストラクタ、メソッドの順に確認するのが目安です。フィールドは状態、コンストラクタは初期化、メソッドは操作という役割を持ちます。この分類を先に置くと、長いサンプルコードでもどの行が何を担当しているか迷いにくくなるのが目安です。

クラスの定義

public class Car { String brand; public void drive() { System.out.println(brand + "が走ります。"); } }

結果: このクラスだけでは出力は発生せず、driveを呼び出すコードを追加すると期待される出力は「ブランド名が走りますし、これが一つの目安です。」になるのがポイントです。

このサンプルコードでは、public class CarCarというクラスを定義しています。String brandは車の状態を保持し、public void drive()はその状態を使った処理になります。ただし、brandへ値を入れないまま呼ぶとnullが出力に含まれる可能性があるのが一般的です。

そのため、クラス定義だけを見た段階では、値がどこから入るのかも確認するのがポイントです。フィールドへ直接代入するのか、コンストラクタで受け取るのか、メソッドで変更するのかによって、オブジェクトの使い方が変わります。Javaプログラミングでは、状態の入口を限定するとコードの追跡がしやすくなるのが現実的です。

クラスの属性とメソッド

属性はオブジェクトの状態を表し、メソッドは状態を読む、変更する、外部へ表示するなどの操作を担います。その関係を分けて考えると、Javaプログラミングでクラスを大きくしすぎる問題にも気付きやすくなるのが一般的です。クラスが多くの責務を持ちすぎた場合は、別のクラスへ役割を分ける設計も候補になると整理できます。

属性の定義方法

public class Car { String brand; int year; double price; }

結果: このコードは属性を持つCarクラスを定義するだけなので、期待される出力はありません。

具体的には、brandが文字列、yearが整数、priceが小数を扱います。型を明示することで、代入できる値の種類がコンパイル時に確認されますが、覚えておくと役立つでしょう。価格を整数だけで扱うならintも使えますが、小数を想定するならdoubleや金額向けの別設計を検討すると理解できます。

これらのフィールドは、何でも追加すればよいわけではありません。車を表すクラスに所有者情報、販売履歴、整備記録まで詰め込むと責務が広がりすぎます。その場合は、OwnerMaintenanceRecordのような別クラスを用意し、関連する情報だけを参照させる構成が読みやすくなるのが現実的です。

メソッドの定義方法

public class Car { String brand; int year; double price; public void setDetails(String brand, int year, double price) { this.brand = brand; this.year = year; this.price = price; } public void drive() { System.out.println(brand + "が走ります。"); } }

結果: setDetailsで値を入れてからdriveを呼ぶと、期待される出力は「指定したブランドが走ります。」という形式になると覚えるとよいでしょう。

このとき、引数名とフィールド名が同じなのでthis.brandのように書いて区別すると整理できます。thisは現在のインスタンスを指すため、どの値へ代入しているかが明確になります。メソッドの戻り値がない場合はvoidを使い、値を返す処理ではreturnと戻り値の型を組み合わせますし、これが一つの目安です。

ただし、値を無条件に受け入れるメソッドは、想定外の状態を作る原因にもなります。例えばyearに未来すぎる値やpriceに負数が入ると、車の情報として不自然になると理解できます。必要に応じてメソッド内で条件分岐を置き、入力値を検査する設計が使われますが、覚えておくと役立つでしょう。

クラスのインスタンス化

public class 車 { String モデル名; int 速度; public void 加速する(int 増速量) { 速度 += 増速量; } }

結果: クラス定義のみなので期待される出力はありませんが、加速するを呼ぶと速度の値が増えます。

この例は日本語の識別子を使っています。JavaではUnicode識別子を扱えますが、チーム開発では英字のCarmodelNamespeedのような命名が選ばれやすくなると覚えるとよいでしょう。ただし、プログラミング初心者が概念をつかむ段階では、意味が直接読める名前にも学習上の利点があると考えられます。

車 マイカー = new 車();

結果: クラスのインスタンスが作成されますが、この行だけでは期待される出力はありません。

その行では、右辺のnew 車()がインスタンスを作り、左辺のマイカーがその参照を受け取ります。参照型の変数はオブジェクトそのものではなく、オブジェクトへ到達するための値を保持すると考えられます。この違いを理解すると、メソッド呼び出し後に同じインスタンスの状態が変わる理由も追いやすくなると言えるでしょう。

マイカー.モデル名 = "トヨタ"; マイカー.速度 = 0; マイカー.加速する(50);

結果: 期待される状態は、モデル名が「トヨタ」、速度50になった状態です。

この操作では、ドット演算子.を使ってインスタンスのフィールドとメソッドへアクセスしています。加速する(50)は引数50を受け取り、速度 += 増速量で状態を更新します。公開するフィールドを直接変更する設計は学習用として分かりやすい一方、実務ではprivateとアクセサメソッドで変更範囲を制御することが多くなるのが基本です。

このような短いサンプルコードでも、クラス、インスタンス、フィールド、メソッド呼び出しの流れがすべて含まれますし、ここを基本と考えるとよいでしょう。Javaのクラスを読むときは、定義と利用を分け、どの行でオブジェクトが生まれ、どの行で状態が変わるかを追跡します。その読み方は、後に継承を扱う場面でもそのまま使えるのが目安です。

Javaの継承とは

Javaの継承は、あるクラスが別のクラスのフィールドやメソッドを引き継ぎ、差分だけを子クラスへ追加する仕組みです。公式チュートリアルのInheritanceでも、サブクラスがスーパークラスから状態と振る舞いを受け継ぐ考え方が説明されています。継承はオブジェクト指向の徹底解説で避けて通れないテーマですが、使いすぎると関係が読みにくくなる点にも注意が必要になると言えるでしょう。

継承の基本概念

一般に、継承は「子クラスは親クラスの一種です」と言える場面に向いています。Truck extends Carなら、トラックは車両の一種として扱えるため、is-a関係が成立しやすくなります。逆に、エンジンを車の子クラスにするような設計は、部品関係であってis-a関係ではないため不自然になるのがポイントです。

そのため、継承を使う前に「共通処理を親へ置く理由」と「子クラスとして扱う理由」を分けて考える必要があるのが基本です。単に同じフィールドを再利用したいだけなら、フィールドとして別オブジェクトを持つコンポジションのほうが自然な場合もあります。Javaプログラミングでは、継承とコンポジションの選択が設計の読みやすさに直結するのが一般的です。

これを判断する目安は、子クラスのインスタンスを親クラスとして扱っても意味が崩れないかどうかです。車両の一覧に乗用車やトラックを並べる設計は自然ですが、車両の一覧にエンジンを混ぜると違和感が出ます。継承のサンプルコードを読むときも、構文だけでなく関係性の妥当性を確認するのが目安です。

継承の利点と使い方

継承を利用したコードの再利用

class Car { String color; int speed; void run() { } void stop() { } } class Truck extends Car { int payload; void loadGoods() { } }

結果: メソッド本体に出力処理がないため期待される出力はありませんが、Truckrunstopを呼び出せます。

このサンプルコードでは、TruckCarを継承し、payloadloadGoodsを追加しています。親クラスに置いたcolorspeedrunstopは子クラス側でも利用できるのがポイントです。ただし、親クラスの変更が子クラスへ影響するため、共通化の範囲は慎重に決めますし、ここを基本と考えるとよいでしょう。

同様に、複数の子クラスが同じ処理を持つなら、親クラスへ寄せることで重複を減らせます。とはいえ、将来の子クラスまで予測して親を大きくすると、使わないメソッドまで引き継ぐ設計になります。Javaの継承では、現在の要件で明確に共通している処理を親へ置く姿勢が扱いやすくなるのが現実的です。

ポリモーフィズムの実現

Car myCar = new Truck();

結果: TruckインスタンスをCar型の変数で参照する状態になり、この行だけでは期待される出力はありません。

この書き方は、親クラス型の変数で子クラスのインスタンスを扱うポリモーフィズムの入口になるのが一般的です。myCar.run()のように親クラスで定義された操作を呼べる一方、loadGoodsのような子クラス固有の操作はCar型のままでは呼べません。この制約は、利用側に見せる操作を絞るという設計上の役割も持ちます。

その設計では、呼び出し側は具体的な子クラス名を知らなくても共通操作を扱えます。例えば車両一覧をCar[]List<Car>で持てば、乗用車とトラックを同じループで処理できるのが現実的です。ポリモーフィズムは抽象的な語に見えますが、呼び出し側の依存を減らすための仕組みとして理解できると整理できます。

継承の注意点と対処法

class 動物 { void 鳴く() { System.out.println("動物が鳴いています"); } } class 犬 extends 動物 { void 鳴く() { System.out.println("犬がワンワンと鳴いています"); } void しっぽを振る() { System.out.println("犬がしっぽを振っています"); } }

結果: 鳴くを呼ぶと、期待される出力は「犬がワンワンと鳴いています」です。

この例では、動物を継承し、鳴くを子クラス側で置き換えています。Javaでは親クラスのprivateメンバーへ子クラスから直接アクセスできないため、必要に応じてprotectedや公開メソッドを検討すると整理できます。一方、公開範囲を広げすぎると状態変更の経路が増えるため、カプセル化とのバランスが必要になると理解できます。

⚠️ 注意: 継承は再利用の手段ですが、親子関係が深くなるほど影響範囲を追いにくくなります。is-a関係が弱い場合は、別クラスをフィールドとして持つ設計も検討すると理解できます。

その対処として、@Overrideを付けて意図したオーバーライドかどうかをコンパイラに確認させます。メソッド名や引数を誤るとオーバーロードになってしまうことがあるため、アノテーションの有無はレビュー時の手掛かりにもなります。詳しい構文はJLS Chapter 8で確認できると覚えるとよいでしょう。

  1. 親子関係がis-aで説明できるか確認する
  2. privateprotectedpublicの範囲を絞る
  3. 深すぎる継承階層を避ける
  4. コンポジションで表せないか比較する
  5. オーバーライドには@Overrideを付ける

ただし、対処法を一覧で覚えるだけでは設計判断につながりません。親子関係を作るたびに、親が変わったとき子クラスへどのような影響が出るかを考えますし、ここがポイントです。この視点を持つと、継承を使う場面と使わない場面を選びやすくなります。

実際のコーディング

実際のコーディングでは、クラスの宣言、フィールド、コンストラクタ、メソッド、インスタンス化の順に読むと流れを追いやすくなります。その順番はJavaプログラミングのサンプルコードを読むときにも有効です。徹底解説として細部まで追う場合でも、最初は「何を持ち、何をするクラスか」を確認すると覚えるとよいでしょう。

クラスの作り方

基本的なクラスの作成

public class Person { String name; int age; public void introduce() { System.out.println("名前は" + name + "で、年齢は" + age + "歳です。"); } }

結果: nameageを設定してintroduceを呼ぶと、期待される出力は「名前は値で、年齢は値歳です。」の形式になります。

このクラスは、人物の状態をnameageで持ち、表示処理をintroduceへまとめています。メソッド内では文字列連結に+を使っていますが、複雑な整形ではString.formatなども候補になると考えられます。クラスの役割が小さいため、プログラミング初心者でもフィールドとメソッドの対応を追いやすい例です。

そのうえで、フィールドを外部から直接変更できる設計にするか、コンストラクタやメソッド経由にするかを選びます。学習用の小さなコードでは直接代入でも流れを追えますが、入力値の検査を入れたい場合はメソッド経由のほうが管理しやすくなります。Javaのクラス設計では、値の入口をどこに置くかが読みやすさに影響すると考えられますが、これは押さえたい点です。

コンストラクタの利用

public class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } public void introduce() { System.out.println("名前は" + name + "で、年齢は" + age + "歳です。"); } }

結果: new Person("山田", 20)のように生成すると、期待される状態はnameが「山田」、age20です。

コンストラクタはクラス名と同じ名前を持ち、戻り値の型を書きません。この仕組みにより、インスタンス作成時点で必要な値をそろえられます。ただし、引数が増えすぎると呼び出し側が読みづらくなるため、役割の分割や別クラス化を検討します。

このとき、this.name = nameは左辺がフィールド、右辺が引数です。同じ名前を使うことで対応関係は見えやすくなりますが、thisを書き忘れると意図しない代入になる場合があると言えるでしょう。コンストラクタの中では、初期状態が不完全にならないよう必要な値をまとめて受け取る設計がよく使われます。

継承を利用したクラスの作成

継承を利用したクラス作成では、親クラスに共通処理を置き、子クラスには差分だけを書きます。その構成にすると、同じ処理を複数箇所へ書く量を減らせますし、これが一つの目安です。一方、親クラスの変更が全子クラスへ伝わるため、共通化する内容は安定した概念に絞るのが現実的です。

基本的な継承の実装

public class 親クラス { public String 属性1 = "親クラスの属性1"; public void メソッド1() { System.out.println("親クラスのメソッド1が呼び出されました"); } }

結果: メソッド1を呼ぶと、期待される出力は「親クラスのメソッド1が呼び出されました」です。

この親クラスは、子クラスへ引き継がれる属性とメソッドを持っています。学習用としてpublicフィールドにしていますが、通常はprivateにしてメソッド経由で扱う設計が読みやすくなります。属性を公開すると、どの処理が値を変えたのか追いにくくなるためです。

public class 子クラス extends 親クラス { public String 属性2 = "子クラスの属性2"; public void メソッド2() { System.out.println("子クラスのメソッド2が呼び出されました"); } }

結果: メソッド2を呼ぶと、期待される出力は「子クラスのメソッド2が呼び出されました」です。

この子クラスはextends 親クラスによって親の要素を引き継ぎますが、覚えておくと役立つでしょう。つまり、子クラスのインスタンスからメソッド1メソッド2の両方を呼べます。継承の徹底解説で特に押さえたいのは、追加された要素と引き継がれた要素を区別して読むことです。

こうした日本語名の例は概念を追うためのものです。実際のJavaプログラミングでは、ParentClassChildClassのように英字で書くと、外部ライブラリやチームの命名規則とそろえやすくなります。名前の形式よりも、親へ置く処理と子へ置く処理の境界を説明できることが大切になるのが基本です。

オーバーライドとオーバーロード

public class 子クラス extends 親クラス { @Override public void メソッド1() { System.out.println("子クラスでメソッド1がオーバーライドされました"); } public void メソッド2() { System.out.println("子クラスのメソッド2が呼び出されました"); } }

結果: 子クラスのメソッド1を呼ぶと、期待される出力は「子クラスでメソッド1がオーバーライドされました」です。

このコードでは、親クラスにあるメソッド1を子クラス側で置き換えています。@Overrideを付けると、親クラスに対応するメソッドが存在しない場合にコンパイルエラーで気付けます。オーバーライドは継承とポリモーフィズムを結び付ける中核的な構文になると言えるでしょう。

public class 親クラス { public void メソッド3(String 引数1) { System.out.println("引数1: " + 引数1); } public void メソッド3(String 引数1, String 引数2) { System.out.println("引数1: " + 引数1 + ", 引数2: " + 引数2); } }

結果: 引数を1個渡すと「引数1: 値」、2個渡すと「引数1: 値, 引数2: 値」という期待される出力になるのが目安です。

オーバーロードは同じメソッド名を使い、引数の数や型で呼び分ける仕組みです。継承がなくても使えるため、オーバーライドとは発生条件が異なります。signatureに相当する引数構成を見れば、どのメソッドが呼ばれるか判断しやすくなります。

一方、オーバーライドは親クラスのメソッドを子クラスで置き換えるため、メソッド名、引数、戻り値の関係を慎重に合わせますし、ここを基本と考えるとよいでしょう。戻り値は共変戻り値として一部の範囲で変更できますが、初心者段階では同じ型にそろえて読むほうが安全です。Javaのサンプルコードでは、@Overrideの有無が両者を見分ける手掛かりになります。

実用的なサンプルコード

継承を利用したプログラムの実例

public class Vehicle { String name; int speed; public Vehicle(String name, int speed) { this.name = name; this.speed = speed; } public void showInfo() { System.out.println("車両名: " + name + "、速度: " + speed + "km/h"); } }

結果: showInfoを呼ぶと、期待される出力は「車両名: 名前、速度: 数値km/h」です。

この親クラスは、車両に共通するnamespeedを持ちます。コンストラクタで初期値を受け取り、showInfoで共通情報を表示します。サンプルコードとしては短いですが、共通項目を親へ置く継承の考え方が表れているのが基本です。

public class Car extends Vehicle { int fuel; public Car(String name, int speed, int fuel) { super(name, speed); this.fuel = fuel; } public void showCarDetails() { System.out.println("車両名: " + name + "、速度: " + speed + "km/h、燃料量: " + fuel + "L"); } }

結果: showCarDetailsを呼ぶと、期待される出力は車両名、速度、燃料量を含む形式になるのがポイントです。

この子クラスでは、super(name, speed)で親クラスのコンストラクタを呼び出しています。子クラス固有のfuelthis.fuelへ代入します。親の初期化と子の初期化を分けて読むと、継承を使ったJavaプログラミングの流れが整理できるのが目安です。

public class Main { public static void main(String[] args) { Car myCar = new Car("プリウス", 100, 50); myCar.showInfo(); myCar.showCarDetails(); } }

結果: 期待される出力は「車両名: プリウス、速度: 100km/h」と「車両名: プリウス、速度: 100km/h、燃料量: 50L」です。

このMainクラスでは、new Car("プリウス", 100, 50)で子クラスのインスタンスを作りますし、ここがポイントです。その後、親クラス由来のshowInfoと子クラス固有のshowCarDetailsを順に呼び出します。関連する日付計算などの題材へ進む場合は、Javaでうるう年を判定する例も条件分岐の練習になるのがポイントです。

この構成では、利用側のMainが車両の内部構造を細かく知る必要はありません。必要な値をコンストラクタへ渡し、公開されたメソッドを呼ぶだけで処理を進められます。オブジェクト指向の利点は、このように生成、状態、操作の境界を読みやすく分けられる点にあるのが一般的です。

応用例とサンプルコード

応用例では、既存クラスに差分を加えるカスタマイズ、抽象クラス、インターフェース、デザインパターンを扱います。これらはクラスと継承の基礎が分かってから読むと理解しやすくなります。一方、プログラミング初心者が一度に全部を暗記する必要はなく、サンプルコードで構造を見分けるところから始めるのが現実的です。

クラスのカスタマイズ

public class CustomizedClass extends ExistingClass { public void newMethod() { System.out.println("新たなメソッドが実行されました"); } }

結果: newMethodを呼ぶと、期待される出力は「新たなメソッドが実行されました」です。

このサンプルコードでは、ExistingClassを継承したCustomizedClassnewMethodを追加しているのが現実的です。既存の振る舞いを残しつつ差分を足したい場合に、この形は理解しやすい構成になります。ただし、親クラスが変更される前提が多いなら、インターフェースを使って依存を弱める設計も候補になるのが一般的です。

💡 Tips: 既存クラスを拡張するときは、継承で表す関係か、部品として持つ関係かを先に言語化します。言語化できない継承は、後から責務が混ざりやすくなると整理できます。

具体的には、既存の処理を少し変えたいだけならオーバーライド、処理を横に増やしたいなら別メソッド、共通の契約だけをそろえたいならインターフェースが候補になります。どれを選ぶかで、テスト対象や修正範囲も変わります。Javaの徹底解説としては、拡張方法を構文名ではなく変更理由と結び付けて覚えるとよいでしょう。

継承のさらなる活用

継承をさらに活用する場面では、共通実装を持つ抽象クラスと、操作の契約を表すインターフェースを使い分けますが、これは押さえたい点です。その違いを押さえると、オブジェクト指向の設計を単なる親子関係だけで考えなくなります。Javaの徹底解説としては、extendsimplementsの違いをコード上で見分けることが要点になるのが現実的です。

抽象クラスとインターフェースの利用

abstract class AbstractClass { abstract void abstractMethod(); } interface InterfaceExample { void interfaceMethod(); } class ImplementationClass extends AbstractClass implements InterfaceExample { @Override void abstractMethod() { System.out.println("抽象メソッドが実装されました"); } @Override public void interfaceMethod() { System.out.println("インターフェースメソッドが実装されました"); } }

結果: 各メソッドを呼ぶと、期待される出力は「抽象メソッドが実装されました」と「インターフェースメソッドが実装されました」です。

このコードでは、AbstractClassextendsし、InterfaceExampleimplementsしています。抽象クラスは一部の実装を共有したい場合に向き、インターフェースは実装クラスが満たす操作を示す場合に向きます。Javaアノテーションを学ぶと、@Overrideなどの意味もより読みやすくなると整理できると理解できます。

その違いは、継承できるクラスが1つだけである点にも表れます。Javaでは単一継承のため、extendsで複数のクラスを同時に親へできません。一方、インターフェースは複数実装できるため、操作の契約を組み合わせたい場合に使いやすくなります。

デザインパターンの一例

public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }

結果: getInstanceを呼ぶと、期待される戻り値は同じSingletonインスタンスへの参照です。

この例はシングルトンパターンの基本形で、privateコンストラクタにより外部からのnew Singleton()を防ぎます。staticフィールドに唯一のインスタンスを保持し、getInstanceから取得します。ただし、並行処理を含む本格的な設計ではスレッド安全性の検討が必要になるため、この形は学習用の最小例として扱いると理解できると覚えるとよいでしょう。

ℹ️ 補足: Javaプログラミングでパターンを学ぶ場合、名前よりも「どの生成経路を閉じ、どの操作だけを外へ見せるか」を読むと理解しやすくなります。

もっとも、デザインパターンは使えば設計が必ず良くなるものではありません。クラスの生成を制限したい、共通の操作をそろえたい、処理の差し替えをしやすくしたいなど、具体的な意図があるときに選びます。サンプルコードを読む際は、パターン名よりも制約と効果の対応を確認すると考えられます。

まとめ

Javaのクラスは、状態を表すフィールドと操作を表すメソッドをまとめる単位です。そのクラスからnewでインスタンスを作り、ドット演算子でフィールドやメソッドへアクセスします。継承を使うと親クラスの共通要素を子クラスへ引き継げますが、is-a関係が弱い場合はコンポジションも検討します。

これらを押さえると、オーバーライド、オーバーロード、抽象クラス、インターフェースの違いも見通しやすくなると言えるでしょう。Javaプログラミングのサンプルコードを読むときは、publicprivatestaticfinalabstractreturnなどの語がどの位置にあるかを確認します。徹底解説として学ぶなら、コードを丸暗記するより、各行の責務を言葉で説明できる状態を目指すとよいでしょう。

そのうえで、プログラミング初心者は短いサンプルコードを手で読み、親クラス由来の処理と子クラス固有の処理を分けて整理します。オブジェクト指向の考え方は抽象的に見えますが、クラス、継承、インスタンス化、メソッド呼び出しを順に追えば具体的に理解できるのが基本です。Javaの基礎が固まったら、コレクション、例外処理、アノテーションへ学習範囲を広げると、既存コードの読解力も上がりますし、ここがポイントです。

同様に、コードを書くときは小さなクラスから始め、必要な共通点が見えてから継承を導入します。最初から大きな親クラスを作るより、重複の理由が説明できる段階で共通化したほうが、変更に耐える構造になりやすいでしょう。Javaプログラミングの学習では、文法と設計判断を切り離さずに読むことが大切になります。

具体的には、クラスを読むときは「何を保持するか」、メソッドを読むときは「どの状態を使い、どの状態を変えるか」を確認するのが目安です。その読み方を続けると、継承で親クラスへ置くべき共通処理と、子クラスへ残すべき固有処理を分けやすくなります。Javaプログラミングの学習では、文法名だけでなく責務の位置を言葉にすることが理解を支えますが、これは押さえたい点です。

一方、すべてを継承で解決しようとすると、親クラスの変更理由が増えすぎる場合があります。共通化したい処理が本当に親子関係を表すのか、単に別部品として持てばよいのかを比較するのがポイントです。この比較を習慣にすると、オブジェクト指向の設計をサンプルコードの模写で終わらせず、実際のコード読解へつなげられますし、これが一つの目安です。

そのため、プログラミング初心者は短いJavaの例で、classextendsnewthissuperが出る場所を何度も確認すると効果的です。クラスと継承の関係を図にしてからサンプルコードへ戻ると、親から引き継ぐ要素と子で追加する要素の境界が見えやすくなります。

これらの確認を積み重ねると、Javaのクラスは単なる文法ではなく、変更の影響範囲を整理する単位として見えてきます。継承はその整理を親子関係へ広げる仕組みであり、適切に使うと同じ処理を重ねて書く量を減らせますし、これが一つの目安です。ただし、関係が曖昧なまま親クラスを増やすと、後から修正箇所を探しにくくなるため、サンプルコードの段階から役割の境界を意識すると覚えるとよいでしょう。

同様に、コードを読む順序も決めておくと負担が下がります。ファイル名とクラス名を確認し、フィールドで状態を見て、コンストラクタで初期化を追い、メソッドで操作の流れを読む形です。この順序なら、Javaのクラスと継承が混ざった長めのコードでも、親から来た処理と子で追加された処理を分けて理解できます。

ただし、読む順序は固定の規則ではなく、迷ったときの補助線です。慣れてきたら、エラーが出ている行、呼び出し元、親クラスの定義へ戻るように読み方を調整するのが一般的です。

この読み戻りを繰り返すと、Javaの継承関係を表面的な構文ではなく、変更を追跡するための構造として扱えるようになります。

確認を続けます。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。