Javaのクラス定義は、プログラミング初心者が最初につまずきやすいポイントの一つです。しかし、この仕組みを理解すると、Javaの本当の面白さが見えてきます。
当記事では、クラスの概念的な話から、具体的な書き方、そして実践的な使い方まで、豊富なサンプルコードを交えて解説を進めます。
この記事を読み終える頃には、クラスへの苦手意識がなくなり、自信を持ってコードを書けるようになっているでしょう。
この記事で学べること
- オブジェクト指向におけるクラスの役割
- Javaのクラスを定義するための基本構文
- フィールドとメソッドの具体的な書き方
- 定義したクラスをインスタンス化して使う方法
- クラスを扱う上で知っておきたい重要ルール
Javaのクラスとは?
ここでは、Javaプログラミングの土台となるクラスの概念について学びます。なぜクラスが必要なのか、その本質的な役割を理解することで、今後の学習がスムーズになります。
- オブジェクト指向とクラスの関係
- クラスは「モノの設計図」と覚えよう
- インスタンスは設計図から作られた「実体」
オブジェクト指向とクラスの関係
Javaはオブジェクト指向という考え方に基づいたプログラミング言語です。オブジェクト指向は、現実世界の「モノ」をプログラムの中で再現しようとする考え方を示します。
例えば、人、車、犬といった現実世界のモノを、プログラム上のデータと処理のまとまりとして扱います。そして、その「モノ」の設計図の役割を果たすのがクラスなのです。
クラスは「モノの設計図」と覚えよう
クラスを一番イメージしやすい例えは、「たい焼きの型」です。
たい焼きを作るとき、まず最初に「型」を用意しますよね。その型には、あんこを入れる場所や、生地を流し込む形が決まっています。この「たい焼きの型」こそがクラスに当たります。
プログラムの世界でも同様に、これから作りたい「モノ」がどんなデータ(材料)を持ち、どんな振る舞い(作り方)をするのかを定義した設計図がクラスというわけです。
インスタンスは設計図から作られた「実体」
設計図であるクラス(たい焼きの型)から実際に作られた「モノ」をインスタンスと呼びます。たい焼きの例でいうと、型から作られた「たい焼きそのもの」がインスタンスです。
一つの型(クラス)があれば、あんこ入り、クリーム入り、チョコレート入りといった、中身の違うたい焼き(インスタンス)を何個も作れます。Javaのクラスとインスタンスも、この関係性と全く同じです。
【クラスとインスタンスの関係】 [ クラス:たい焼きの型 ] │ ├── new ─────────→ [ インスタンス1:あんこ味 ] │ ├── new ─────────→ [ インスタンス2:クリーム味 ] │ └── new ─────────→ [ インスタンス3:チョコ味 ]
Javaのクラス定義の書き方
クラスの概念を理解したところで、いよいよJavaでクラスを定義する具体的な書き方を見ていきましょう。ここでは、最もシンプルなクラスの作り方と、命名規則について解説します。
- 基本構文:`class`キーワードを使う
- クラス名の付け方のルール
基本構文:`class`キーワードを使う
Javaでクラスを定義するには、`class`というキーワードを使います。構文は非常にシンプルです。
// 最もシンプルなクラス定義
class User {
// この中に、データや処理を書いていく
}
`class`のあとに、自分で決めたクラス名を記述し、その後ろに波括弧`{}`を続けます。この波括弧の中に、そのクラスが持つデータや処理を定義していくことになります。まずは、この基本の形を覚えましょう。
クラス名の付け方のルール
クラス名は自由に付けられますが、誰が見ても分かりやすいコードを書くために、いくつかの命名ルールが存在します。特に重要なルールは以下の通りです。
クラス名の先頭は大文字にし、単語の区切りも大文字にする「パスカルケース(またはアッパーキャメルケース)」を使うのが一般的です。
| 分類 | 例 |
|---|---|
| 良い例 (パスカルケース) | `User`, `ProductItem`, `Main` |
| 避けるべき例 | `user`(小文字始まり), `product_item`(スネークケース) |
クラスの三大要素!フィールド・メソッド・コンストラクタを定義しよう
シンプルなクラスの箱を作れるようになったら、次はその中身を定義していきます。クラスの中身は、主に「フィールド」「メソッド」「コンストラクタ」という3つの要素で構成されます。それぞれの役割と書き方を学びましょう。
- フィールド(メンバ変数)を定義する
- メソッド(メンバ関数)を定義する
- コンストラクタを定義する
フィールド(メンバ変数)を定義する
フィールドは、クラスが持つデータ(状態や属性)を定義する部分です。変数と同じように宣言します。「会員」クラスであれば、名前や年齢などがフィールドに当たります。
class User {
// フィールド (データ)
String name; // 名前を保存する
int age; // 年齢を保存する
}
メソッド(メンバ関数)を定義する
メソッドは、クラスが行う処理(振る舞い)を定義する部分です。例えば、「会員」クラスであれば、自己紹介をするといった処理がメソッドに当たります。
class User {
// フィールド
String name;
int age;
// メソッド (処理)
void introduce() {
System.out.println("こんにちは、" + name + "です。年齢は" + age + "歳です。");
}
}
コンストラクタを定義する
コンストラクタは、クラスからインスタンスを生成する際に、フィールドを初期化するための特殊なメソッドです。`new`キーワードでインスタンスが作られるときに一度だけ呼び出されます。
コンストラクタは、メソッド名がクラス名と全く同じで、戻り値がないという特徴を持ちます。
class User {
// フィールド
String name;
int age;
// コンストラクタ
User(String n, int a) {
System.out.println("コンストラクタが呼ばれました。");
name = n;
age = a;
}
// メソッド
void introduce() {
System.out.println("こんにちは、" + name + "です。年齢は" + age + "歳です。");
}
}
定義したJavaクラスを実際に使ってみよう
設計図(クラス)が完成したら、それを使って実体(インスタンス)を作り、実際に動かしてみます。ここでは、クラスをインスタンス化する方法と、そのインスタンスのデータや処理を利用する方法を解説します。
- `new`演算子でインスタンス化する
- インスタンスのフィールドにアクセスする
- インスタンスのメソッドを呼び出す
`new`演算子でインスタンス化する
クラスからインスタンスを生成するには、`new`演算子を使います。`new`のあとに、呼び出したいコンストラクタを記述します。
// Userクラスのインスタンスを生成し、変数user1に代入する
User user1 = new User("山田太郎", 25);
この一行で、`User`クラスの設計図を元に、「山田太郎、25歳」というデータを持ったインスタンスがメモリ上に作成されます。
インスタンスのフィールドにアクセスする
インスタンスが持つフィールド(データ)にアクセスするには、「インスタンス名.フィールド名」と記述します。これにより、フィールドの値を参照したり、新しい値を代入したりできます。
// user1インスタンスのageフィールドの値を表示する System.out.println(user1.age); // 結果:25 // user1インスタンスのageフィールドに新しい値を代入する user1.age = 26; System.out.println(user1.age); // 結果:26
インスタンスのメソッドを呼び出す
メソッドの呼び出しもフィールドへのアクセスと似ています。「インスタンス名.メソッド名()」と記述することで、そのインスタンスのメソッドを実行できます。
では、クラス定義からインスタンス化、メソッド呼び出しまでの一連の流れを、2つのファイルに分けて見てみましょう。
User.java(クラス定義ファイル)
class User {
String name;
int age;
User(String n, int a) {
name = n;
age = a;
}
void introduce() {
System.out.println("こんにちは、" + name + "です。年齢は" + age + "歳です。");
}
}
Main.java(実行用ファイル)
class Main {
public static void main(String[] args) {
// Userクラスから2つのインスタンスを生成
User user1 = new User("山田太郎", 25);
User user2 = new User("鈴木花子", 30);
// 各インスタンスのメソッドを呼び出す
user1.introduce();
user2.introduce();
}
}
実行結果
こんにちは、山田太郎です。年齢は25歳です。 こんにちは、鈴木花子です。年齢は30歳です。
Javaのクラス定義で知っておきたい7つの重要ルール
クラスの基本的な使い方が分かったら、次は一歩進んだルールを学びます。アクセス修飾子や`this`キーワードなどを理解することで、より安全で効率的なコードを書けるようになります。
- アクセス修飾子で公開範囲を決める (`public`, `private`など)
- `this`キーワードで自分自身のインスタンスを指し示す
- `static`キーワードでクラス共通のメンバを定義する
- `final`キーワードで変更不可な定数を定義する
- クラスを継承して機能を拡張する (`extends`)
- インターフェースで振る舞いを定義する (`implements`)
- パッケージでクラスを整理・分類する
アクセス修飾子で公開範囲を決める (`public`, `private`など)
アクセス修飾子は、フィールドやメソッドがクラスの外部からアクセスできる範囲を制限するためのキーワードです。意図しない値の書き換えを防ぎ、安全なプログラムを作るために非常に重要です。
| 修飾子 | アクセス範囲 |
|---|---|
| public | どこからでもアクセス可能 |
| protected | 同じパッケージとサブクラスからアクセス可能 |
| (何もつけない) | 同じパッケージ内からのみアクセス可能 |
| private | 同じクラス内からのみアクセス可能 |
引用元:Oracle社の公式Javaチュートリアル - Controlling Access to Members of a Class
`this`キーワードで自分自身のインスタンスを指し示す
`this`は、メソッドやコンストラクタ内で、呼び出し元のインスタンス自身を指し示すためのキーワードです。特に、コンストラクタの引数名とフィールド名が同じ場合に、両者を区別するために使われます。
class User {
String name;
int age;
User(String name, int age) {
// this.nameはフィールド、nameは引数を指す
this.name = name;
this.age = age;
}
}
`static`キーワードでクラス共通のメンバを定義する
`static`を付けて定義されたフィールドやメソッドは、インスタンスごとではなく、クラスに直接関連付けられます。すべてのインスタンスで共有されるデータを扱う際に便利です。例えば、作成されたユーザー数をカウントする、といった用途で使われます。
class User {
static int userCount = 0; // 静的フィールド
User() {
userCount++; // インスタンスが作られるたびにカウントアップ
}
}
`final`キーワードで変更不可な定数を定義する
`final`を付けて宣言された変数は、一度値を代入すると、その後は変更できなくなります。プログラム中で変わることのない値(定数)を定義する際に使用します。例えば、消費税率や円周率などです。
クラスを継承して機能を拡張する (`extends`)
継承は、オブジェクト指向の大きな柱の一つで、既存のクラスが持つフィールドやメソッドを丸ごと引き継いで、新しいクラスを作成できる仕組みです。
この仕組みのおかげで、コードの重複をなくし、プログラムの拡張や修正を簡単に行えるようになります。
引き継がれる元のクラスを「親クラス」や「スーパークラス」と呼び、新しく作成されるクラスを「子クラス」や「サブクラス」と呼びます。
【継承のイメージ】
[ 親クラス:Animal ]
- String name;
- void eat()
- void cry()
│
└── extends (継承)
│
[ 子クラス:Dog ]
(Animalの機能をすべて持つ)
- void run() // Dog独自の機能を追加
- void cry() // 親の機能を上書き(オーバーライド)
継承を利用するには、子クラスの定義時に`extends`キーワードを使います。例を見てみましょう。
Animal.java (親クラス)
// 動物クラス
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + "は食事をしました。");
}
void cry() {
System.out.println("......"); // 動物の共通の鳴き声(とりあえず)
}
}
Dog.java (子クラス)
// Animalクラスを継承したDogクラス
class Dog extends Animal {
// 子クラスのコンストラクタ
Dog(String name) {
// super()で親クラスのコンストラクタを呼び出す
super(name);
}
// 親クラスのメソッドを上書き (オーバーライド)
@Override
void cry() {
System.out.println(name + "は「ワン!」と鳴いた。");
}
}
子クラスでは、親クラスの機能を上書きして、独自の振る舞いに変更できます。これをオーバーライドと呼びます。また、子クラスのコンストラクタでは`super()`を使って、親クラスのコンストラクタを呼び出す必要があります。これにより、親クラスが持つフィールドの初期化が行われます。
このように継承を使うことで、「動物」という共通の機能は`Animal`クラスにまとめておき、「犬」や「猫」といった個別のクラスでは、それぞれに特化した機能の追加や変更に集中できます。
インターフェースで振る舞いを定義する (`implements`)
インターフェースは、クラスが持つべきメソッドの「仕様」や「ルール」だけを定義した、特別な設計図のようなものです。継承が「is-a」の関係(犬は動物の一種である)を表すのに対し、インターフェースは「can-do」の関係(そのクラスは何ができるか)を表現します。
Javaではクラスの多重継承(複数の親クラスを持つこと)はできませんが、インターフェースは一つのクラスで複数実装できます。これにより、柔軟な設計が可能になります。
例えば、「泳ぐ」という機能を考えてみましょう。「人間」も「犬」も「ロボット」も泳げますが、これらは全く別の種類のものです。これらを継承でまとめるのは不自然です。そこで、「泳げる」という共通の機能(仕様)をインターフェースとして定義します。
インターフェースを定義するには`interface`キーワードを、クラスに実装するには`implements`キーワードを使います。
Swimmable.java (インターフェース)
// 「泳げる」という仕様を定義するインターフェース
interface Swimmable {
// 仕様だけを定義(中身は書かない抽象メソッド)
void swim();
}
Person.java (実装クラス)
// Swimmableインターフェースを実装したPersonクラス
class Person implements Swimmable {
String name;
Person(String name) {
this.name = name;
}
// インターフェースで定義されたswimメソッドを必ず実装する
@Override
public void swim() {
System.out.println(name + "はクロールで泳ぎます。");
}
}
Fish.java (実装クラス)
// Swimmableインターフェースを実装したFishクラス
class Fish implements Swimmable {
String type;
Fish(String type) {
this.type = type;
}
// こちらもswimメソッドを必ず実装する
@Override
public void swim() {
System.out.println(type + "はヒレを使ってスイスイ泳ぎます。");
}
}
このようにインターフェースを使うことで、「泳げるものなら必ず`swim()`メソッドを持つ」というルールをプログラム全体で強制できます。これにより、異なる種類のクラスでも同じように扱うことができ、統一感のある設計が実現可能です。
パッケージでクラスを整理・分類する
パッケージは、たくさんのクラスを機能や役割ごとにフォルダ分けして整理するための仕組みです。
大規模なアプリケーション開発では、クラスの数が数百、数千になることも珍しくありません。パッケージを使わないと、全てのクラスが同じ場所に散らかり、管理が非常に困難になります。
パッケージには大きく分けて2つの重要な役割があります。
- クラスの分類・整理:関連するクラスを一つのグループにまとめることで、プロジェクトの構造が分かりやすくなります。
- 名前の衝突を回避:異なるパッケージに属していれば、同じ名前のクラスを定義できます。例えば、`com.example.data.User`と`com.example.api.User`のように区別が可能です。
【パッケージとディレクトリ構造】
my-project/
└─ src/
└─ com/
└─ example/
├─ model/ (パッケージ:com.example.model)
│ ├─ User.java
│ └─ Product.java
│
├─ util/ (パッケージ:com.example.util)
│ └─ DateFormatter.java
│
└─ Main.java (パッケージ:com.example)
あるクラスを特定のパッケージに所属させるには、Javaファイルの一番最初に`package`文を記述します。
User.java
package com.example.model; // このクラスはcom.example.modelパッケージに所属
public class User {
// ...
}
そして、他のパッケージにあるクラスを利用する場合は、`import`文を使ってそのクラスを読み込みます。
Main.java
package com.example;
// com.example.modelパッケージにあるUserクラスをインポート
import com.example.model.User;
// com.example.utilパッケージにある全てのクラスをインポート
import com.example.util.*;
public class Main {
public static void main(String[] args) {
// インポートしたので、クラス名だけで利用できる
User user = new User();
}
}
パッケージを適切に利用することは、自分だけでなく、他の開発者にとっても見通しの良い、メンテナンス性の高いコードを作るための基本作法です。
Javaのクラス定義でよくあるエラーと解決策
ここでは、初心者がJavaのクラス定義で遭遇しやすい代表的なエラーとその解決策を紹介します。エラーメッセージの意味を理解すれば、問題解決のスピードが格段に上がります。
- `cannot find symbol` エラーの原因と対策
- `NullPointerException` を回避する方法
`cannot find symbol` エラーの原因と対策
このエラーは、コンパイラが変数名やメソッド名を見つけられないときに発生します。原因のほとんどは単純なタイプミスです。
`cannot find symbol` の主な原因は、変数名やメソッド名のスペルミス、あるいは変数が宣言される前に使用しようとしているケースです。
エラーメッセージに表示された箇所と、元の定義をよく見比べて、名前が一致しているか、宣言が済んでいるかを確認しましょう。
`NullPointerException` を回避する方法
これはJavaプログラマが最もよく目にする実行時エラーの一つです。`null`、つまり中身が空っぽの変数に対して、フィールドにアクセスしたりメソッドを呼び出したりしようとしたときに発生します。
NullPointerExceptionを防ぐ鉄則
このエラーを防ぐ最も確実な方法は、インスタンスを扱う変数は、必ず`new`を使って初期化してから使うことです。変数を宣言しただけの状態では、中身は`null`のままです。インスタンスを生成して初めて、その変数に実体が宿ります。
もし変数の中身が`null`になる可能性がある場合は、`if`文を使って`null`でないことを確認してから処理を行うのが定石です。
User user = null; // この時点では中身が空
// nullチェックを行う
if (user != null) {
user.introduce(); // この処理は実行されない
}
まとめ
当記事では、Javaのクラス定義について、基本的な概念から実践的な使い方、そして注意点までを網羅的に解説しました。最後に、重要なポイントを振り返ります。
- クラスは「モノの設計図」、インスタンスは「実体」である。
- クラスは主にフィールド(データ)とメソッド(処理)で構成される。
- `new`キーワードを使ってクラスからインスタンスを生成する。
- アクセス修飾子を使って、外部からのアクセスを適切に管理する。
- `null`の変数に対して操作を行うと`NullPointerException`が発生する。
クラスの概念をしっかり理解することは、Javaプログラミングを上達させるための最も重要な一歩です。
この基礎が固まれば、継承やポリモーフィズムといった、より高度なオブジェクト指向の概念もスムーズに理解できるようになるでしょう。


0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。