- JAVA
Java クラスとインスタンスとは|newでの作り方とフィールド・メソッド
Javaのクラスとは「どんなデータ(フィールド)と処理(メソッド)を持つか」を定義した設計図で、インスタンスとは、その設計図から new で作った実体です。 1つのクラスからインスタンスはいくつでも作れ、それぞれが自分のデータを別々に持ちます。
この記事では、会員を表す Member クラス1つを題材に、フィールドの定義 → コンストラクタ → newでの生成 → メソッドの呼び出しまでを順に組み立てます。サンプルはすべてOpenJDK 21.0.10で java ファイル名.java として動かし、出力をそのまま載せました。同じ参照を2つの変数に入れたときの動きや、new忘れによるNullPointerExceptionも再現しています。
クラス・インスタンス・オブジェクト・staticの早見表
| 用語 | ひとことで言うと | コード上の姿 | 個数 |
|---|---|---|---|
| クラス | 設計図(型) | class Member { ... } |
1つ |
| インスタンス | 設計図から作った実体 | new Member("佐藤", 100) で作られるもの |
newした回数ぶん |
| オブジェクト | インスタンスとほぼ同じ意味で使う語 | 同上(配列などを含む、やや広い呼び方) | 同上 |
| インスタンス化 | クラスからインスタンスを作ること | new クラス名(引数) |
― |
| フィールド | インスタンスが持つデータ | String name; int points; |
インスタンスごとに1組 |
| メソッド | インスタンスの処理 | void addPoints(int v) { ... } |
定義は1つ、動作はインスタンスごと |
| コンストラクタ | newのときに呼ばれる初期化処理 | Member(String name, int points) { ... } |
引数違いで複数定義できる |
| 参照 | インスタンスの場所を指す値 | Member m の変数mに入っているもの |
変数ごと |
| staticメンバー | クラスそのものに属するデータ・処理 | static int count; static int getCount() |
クラスに1つ(全インスタンス共通) |
基本の書き方は次の3行です。
| やりたいこと | 書き方 | 例 |
|---|---|---|
| インスタンスを作る | クラス名 変数名 = new クラス名(引数); |
Member m = new Member("佐藤", 100); |
| フィールドを使う | 変数名.フィールド名 |
m.points |
| メソッドを呼ぶ | 変数名.メソッド名(引数) |
m.addPoints(50); |
つまずきやすい点と表示されるメッセージです。
| メッセージ・症状 | 主な原因 | 直し方 |
|---|---|---|
NullPointerException: Cannot read field ... is null |
newしていない変数(null)のフィールドやメソッドを使った | newしたインスタンスを代入する |
variable m might not have been initialized |
ローカル変数に何も代入しないまま使った | = new Member(...) で初期化する |
| 片方の変数で書き換えたら、もう片方も変わった | b = a で同じインスタンスを指している |
別のインスタンスをnewして値を写す |
points has private access in Member |
privateフィールドをクラスの外から直接さわった | メソッド経由で読み書きする |
constructor Member ... cannot be applied to given types |
定義したコンストラクタと引数が合わない | 定義どおりの引数でnewする |
non-static variable name cannot be referenced from a static context |
インスタンスのフィールドを クラス名.フィールド名 で使った |
インスタンスの変数経由で使う |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java クラスとインスタンスとは|newでの作り方とフィールド・メソッド」
クラスとインスタンスの関係
クラスは「型」、インスタンスは「その型の値の実体」です。 int という型から3や100といった値ができるように、Member というクラスから「佐藤さん」「鈴木さん」というインスタンスができます。
flowchart LR
C["クラス Member<br>name / points<br>addPoints()"]
C -- new --> I1["インスタンス1<br>name=佐藤<br>points=100"]
C -- new --> I2["インスタンス2<br>name=鈴木<br>points=30"]
C -- new --> I3["インスタンス3<br>name=高橋<br>points=250"]
| 観点 | クラス | インスタンス |
|---|---|---|
| 役割 | 持つデータの種類と処理を決める | 実際のデータを持つ |
| 作り方 | ソースコードに class で書く |
実行時に new で作る |
| 作られるタイミング | コンパイル時に定義が決まる | プログラムの実行中 |
| データ | 「nameとpointsを持つ」という定義だけ | 「佐藤、100」という具体的な値 |
クラスを使うと、関連するデータと、そのデータを扱う処理を1か所にまとめられます。 会員の名前とポイントを別々の変数や配列で管理するよりも、「会員」という1つのまとまりとして扱えるようになります。
手順1:フィールドを定義してnewする
いちばん小さなクラスは、フィールドだけを並べたものです。 new クラス名() でインスタンスを作り、変数名.フィールド名 で値を読み書きします。
public class Step1 {
public static void main(String[] args) {
Member m = new Member(); // インスタンスを作る
m.name = "佐藤"; // フィールドに値を入れる
m.points = 100;
System.out.println(m.name + " さんのポイント: " + m.points);
Member empty = new Member(); // 何も入れていないインスタンス
System.out.println(empty.name + " / " + empty.points + " / " + empty.active);
}
}
class Member {
String name; // フィールド(インスタンスごとのデータ)
int points;
boolean active;
}
$ java Step1.java
佐藤 さんのポイント: 100
null / 0 / false
この記事では、1つのファイルにmainを持つクラスと Member クラスを並べて書き、java ファイル名.java で動かしています(mainを持つクラスを先頭に書きます)。クラスごとにファイルを分ける場合は、Member.java に Member クラスを書いて javac でコンパイルします。
Member m = new Member(); の読み方
| 部分 | 意味 |
|---|---|
Member m |
Member型の変数mを用意する(インスタンスの場所を入れる変数) |
new Member() |
Memberのインスタンスをメモリ上に作り、コンストラクタを呼ぶ |
= |
作ったインスタンスの場所(参照)を変数mに入れる |
変数mに入っているのはインスタンスそのものではなく、インスタンスの場所を指す「参照」です。 この点が、後で説明する「2つの変数が同じインスタンスを指す」動きにつながります。
フィールドの初期値
2つ目のインスタンス empty は何も代入していませんが、null / 0 / false と表示されました。フィールドは、値を入れなくても型ごとの初期値が入ります。
| フィールドの型 | 初期値 |
|---|---|
| int、long、short、byte | 0 |
| double、float | 0.0 |
| boolean | false |
| char | 文字コード0の文字 |
| String、配列、クラス型(参照型) | null |
メソッドの中で宣言するローカル変数には、この自動の初期値がありません。代入せずに使うとコンパイルエラーになります(後半で再現します)。
手順2:コンストラクタで初期値を渡す
コンストラクタは、newのときに自動で呼ばれる初期化用の処理です。クラスと同じ名前で、戻り値の型を書きません。 newと同時に値を渡せるので、「名前が入っていない会員」ができることを防げます。
public class Step2 {
public static void main(String[] args) {
Member a = new Member("佐藤", 100);
Member b = new Member("鈴木");
System.out.println(a.name + ": " + a.points);
System.out.println(b.name + ": " + b.points);
}
}
class Member {
String name;
int points;
// コンストラクタ:new のときに呼ばれ、フィールドを初期化する
Member(String name, int points) {
this.name = name; // this.name はフィールド、name は引数
this.points = points;
System.out.println("コンストラクタ実行: " + name);
}
// 引数違いのコンストラクタ(オーバーロード)
Member(String name) {
this(name, 0); // 上のコンストラクタを呼ぶ
}
}
コンストラクタ実行: 佐藤
コンストラクタ実行: 鈴木
佐藤: 100
鈴木: 0
newした時点でコンストラクタが動いていることが、1〜2行目の出力からわかります。new Member("鈴木") は引数1つのコンストラクタを通り、そこから this(name, 0) で引数2つのコンストラクタを呼んでいます。引数の型や個数が違えば、コンストラクタは複数定義できます(オーバーロード)。
thisの意味と、付け忘れたときの動き
this は「いま処理しているインスタンス自身」を指します。 引数とフィールドが同じ名前のとき、this.name と書くとフィールド、name だけだと引数を指します。thisを付け忘れるとどうなるかを確かめます。
public class ThisMiss {
public static void main(String[] args) {
Member m = new Member("佐藤", 100);
System.out.println(m.name + " / " + m.points);
}
}
class Member {
String name;
int points;
Member(String name, int points) {
name = name; // 引数を引数自身に代入しているだけ
points = points;
}
}
null / 0
コンパイルは通りますが、フィールドには何も入らず、初期値のnullと0が表示されました。同じ名前が2つあるときは、内側(引数)が優先されます。 フィールドに代入するときは this. を付ける、と決めておくと取り違えが起きません。
| 書き方 | 指すもの |
|---|---|
this.name |
このインスタンスのフィールド name |
name(同名の引数がある場合) |
引数の name |
name(同名の引数がない場合) |
フィールドの name(thisを省略した形) |
this(引数) |
同じクラスの別のコンストラクタ(Java 21ではコンストラクタの先頭に書く) |
コンストラクタを書くと、引数なしのnewはできなくなる
コンストラクタを1つも書かないクラスには、引数なしのコンストラクタが自動で用意されます(手順1で new Member() と書けたのはそのためです)。自分でコンストラクタを定義すると、この自動のコンストラクタは用意されなくなります。
public class NoDefault {
public static void main(String[] args) {
Member m = new Member();
}
}
class Member {
String name;
Member(String name) { this.name = name; }
}
NoDefault.java:3: error: constructor Member in class Member cannot be applied to given types;
Member m = new Member();
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
1 error
error: compilation failed
required が定義側の引数、found がnewで渡した引数です。直し方は、new Member("佐藤") と定義どおりに渡すか、引数なしのコンストラクタ Member() { } を追加するかのどちらかです。
手順3:メソッドを定義して呼び出す
インスタンスメソッドは、そのインスタンスのフィールドを使って動く処理です。 変数名.メソッド名() で呼ぶと、その変数が指すインスタンスのデータに対して実行されます。ここでフィールドを private にして、完成形にします。
public class Step3 {
public static void main(String[] args) {
Member sato = new Member("佐藤", 100);
Member suzuki = new Member("鈴木", 30);
sato.addPoints(50); // sato のポイントだけ増える
suzuki.addPoints(5);
System.out.println(sato.introduce());
System.out.println(suzuki.introduce());
System.out.println("佐藤さんは特典対象? " + sato.isGold());
System.out.println("鈴木さんは特典対象? " + suzuki.isGold());
System.out.println(sato); // toString() が自動で呼ばれる
}
}
class Member {
private String name; // private:クラスの外から直接さわれない
private int points;
Member(String name, int points) {
this.name = name;
this.points = points;
}
// インスタンスメソッド:そのインスタンスのフィールドを使う
void addPoints(int value) {
if (value <= 0) {
return; // 0以下は受け付けない
}
this.points += value;
}
boolean isGold() {
return points >= 100;
}
String introduce() {
return name + "(" + points + "pt)";
}
String getName() { return name; } // 読み取り用(getter)
int getPoints() { return points; }
@Override
public String toString() {
return "Member[name=" + name + ", points=" + points + "]";
}
}
佐藤(150pt)
鈴木(35pt)
佐藤さんは特典対象? true
鈴木さんは特典対象? false
Member[name=佐藤, points=150]
sato.addPoints(50) は佐藤さんのポイントだけを増やし、鈴木さんのポイントには影響していません。同じメソッドでも、どのインスタンスに対して呼んだかによって、使われるデータが変わります。
フィールドをprivateにする理由
フィールドを private にすると、クラスの外から直接代入できなくなります。
Member m = new Member("佐藤", 100);
m.points = 99999;
PrivateErr.java:4: error: points has private access in Member
m.points = 99999;
^
1 error
error: compilation failed
値の変更を addPoints のようなメソッドに限ることで、「0以下は受け付けない」といったルールを必ず通せます。 データと、それを守る処理をひとまとめにするこの考え方をカプセル化と呼びます。
| 書き方 | 外から読む | 外から書き換える | 向いている場面 |
|---|---|---|---|
| フィールドをそのまま公開 | m.points |
m.points = 5 |
学習の最初、小さな確認用コード |
| private+getter | m.getPoints() |
できない | 読み取りだけ許可したい |
| private+操作用メソッド | m.getPoints() |
m.addPoints(5) |
ルールを通して変更させたい |
toStringを定義するとprintlnで中身が見える
System.out.println(sato) と書くと、toString() メソッドが自動で呼ばれます。上の例では自分で定義したので Member[name=佐藤, points=150] と表示されました。定義していないクラスでは、次のような表示になります。
public class NoToString {
public static void main(String[] args) {
Plain p = new Plain();
String s = p.toString();
System.out.println(s.startsWith("Plain@"));
System.out.println(s.replaceAll("@[0-9a-f]+", "@(16進数)"));
}
}
class Plain { }
true
Plain@(16進数)
toStringを定義していないと、「クラス名@16進数」の形で表示されます(16進数の部分は動かすたびに変わるため、ここでは置き換えて表示しています)。中身を確かめたいクラスには、toStringを書いておくと便利です。
同じ参照を2つの変数に代入したときの動き
クラス型の変数を別の変数に代入すると、コピーされるのは参照(場所)です。インスタンスは増えません。 3つの変数で確かめます。
public class RefShare {
public static void main(String[] args) {
Member a = new Member("佐藤", 100);
Member b = a; // 参照をコピー(インスタンスは1つ)
Member c = new Member("佐藤", 100); // 中身が同じ別のインスタンス
b.points += 50; // b 経由で書き換える
System.out.println("a.points = " + a.points);
System.out.println("b.points = " + b.points);
System.out.println("c.points = " + c.points);
System.out.println("a == b : " + (a == b));
System.out.println("a == c : " + (a == c));
b = null; // b が何も指さなくなる
System.out.println("b = " + b + ", a.points = " + a.points);
}
}
class Member {
String name;
int points;
Member(String name, int points) { this.name = name; this.points = points; }
}
a.points = 150
b.points = 150
c.points = 100
a == b : true
a == c : false
b = null, a.points = 150
flowchart LR
a[変数 a] --> X["インスタンス<br>佐藤 / 150"]
b[変数 b] --> X
c[変数 c] --> Y["インスタンス<br>佐藤 / 100"]
出力からわかることを整理します。
| 確かめたこと | 結果 | 理由 |
|---|---|---|
| bで書き換えた後のa | 150(aも変わった) | aとbは同じインスタンスを指している |
| c | 100(変わらない) | cは別にnewしたインスタンス |
a == b |
true | == は「同じインスタンスを指しているか」を比べる |
a == c |
false | 中身が同じでも、別のインスタンス |
b = null の後のa |
150のまま使える | bの参照を外しただけで、インスタンスは残っている |
== はインスタンスの中身ではなく、参照が同じかどうかを比べます。 中身で比べたいときは equals メソッドを使いますが、自作のクラスでは equals を定義していないと == と同じ結果になります(今回の Member で a.equals(c) に当たる比較を別途動かしたところ、falseでした)。
別のインスタンスとしてコピーする
独立したコピーが欲しいときは、newで新しいインスタンスを作り、値を写します。
public class CopyInstance {
public static void main(String[] args) {
Member a = new Member("佐藤", 100);
Member copy = new Member(a); // 中身を写した別のインスタンス
copy.points += 50;
System.out.println("a.points = " + a.points);
System.out.println("copy.points = " + copy.points);
System.out.println("a == copy : " + (a == copy));
}
}
class Member {
String name;
int points;
Member(String name, int points) { this.name = name; this.points = points; }
Member(Member other) { // コピー用のコンストラクタ
this(other.name, other.points);
}
}
a.points = 100
copy.points = 150
a == copy : false
| 書き方 | インスタンスの数 | 片方を書き換えたとき |
|---|---|---|
Member b = a; |
1つ | もう片方からも変わって見える |
Member copy = new Member(a); |
2つ | もう片方は変わらない |
メソッドの引数にインスタンスを渡したときも、渡されるのは参照のコピーです。メソッドの中でフィールドを書き換えると、呼び出し元の変数からも変わって見えます。
new忘れによるNullPointerExceptionを再現する
nullは「どのインスタンスも指していない」ことを表す値です。nullの変数に対してフィールドやメソッドを使うと、実行時にNullPointerExceptionが発生します。 よくある3つの形を再現します。
形1:フィールドを宣言しただけでnewしていない
public class Npe1 {
static Member leader; // new していないフィールド → null
public static void main(String[] args) {
System.out.println("leader = " + leader);
System.out.println(leader.name); // ここで例外
}
}
class Member {
String name;
int points;
Member(String name, int points) { this.name = name; this.points = points; }
}
leader = null
Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "Npe1.leader" is null
at Npe1.main(Npe1.java:6)
メッセージの読み方は次のとおりです。
| 部分 | 意味 |
|---|---|
Cannot read field "name" |
nameフィールドを読もうとしたができなかった |
because "Npe1.leader" is null |
Npe1クラスのleaderがnullだったため |
at Npe1.main(Npe1.java:6) |
Npe1.javaの6行目で発生した |
「何がnullだったか」がメッセージに書かれているので、その変数にnewしたインスタンスを代入すれば直ります(static Member leader = new Member("佐藤", 100);)。
形2:配列を作っただけで、要素をnewしていない
public class Npe2 {
public static void main(String[] args) {
Member[] members = new Member[2]; // 入れ物を2つ作っただけ
System.out.println(members[0]);
members[0].points = 10; // ここで例外
}
}
class Member {
String name;
int points;
}
null
Exception in thread "main" java.lang.NullPointerException: Cannot assign field "points" because "<local1>[0]" is null
at Npe2.main(Npe2.java:5)
new Member[2] は「Memberの参照を2つ入れられる配列」を作るだけで、Memberのインスタンスは作りません。各要素の初期値はnullです。
メッセージ中の <local1> は、java ファイル名.java の形で動かしたために変数名の情報が含まれなかったことによる表示です。javac -g Npe2.java でコンパイルしてから java Npe2 で動かすと、同じ箇所が because "members[0]" is null と変数名つきで表示されました。
直した形です。
public class Npe2Fixed {
public static void main(String[] args) {
Member[] members = new Member[2];
for (int i = 0; i < members.length; i++) {
members[i] = new Member(); // 要素ごとに new する
}
members[0].points = 10;
System.out.println(members[0].points + " / " + members[1].points);
}
}
10 / 0
形3:newしたインスタンスの中のフィールドがnull
public class Npe4 {
public static void main(String[] args) {
Team t = new Team();
System.out.println(t.leader.name.length());
}
}
class Team {
Member leader = new Member(); // Member は new 済み
}
class Member {
String name; // name は null のまま
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>.leader.name" is null
at Npe4.main(Npe4.java:4)
t.leader.name.length() のようにドットが続く式でも、どの部分がnullだったか(ここでは .leader.name)がメッセージに示されます。 t と leader はnew済みで、name に値を入れていなかったことがわかります。
ローカル変数の場合はコンパイルエラーになる
メソッドの中で宣言した変数は、代入せずに使うとコンパイルの段階で止まります。
public static void main(String[] args) {
Member m;
m.points = 10;
}
Npe3.java:4: error: variable m might not have been initialized
m.points = 10;
^
1 error
error: compilation failed
| 変数の種類 | newを忘れたとき | 気づくタイミング |
|---|---|---|
| ローカル変数(代入なし) | variable ... might not have been initialized |
コンパイル時 |
| フィールド | 初期値nullが入り、使った時点でNullPointerException | 実行時 |
| 配列の要素 | 初期値nullが入り、使った時点でNullPointerException | 実行時 |
明示的に = null を代入した変数 |
使った時点でNullPointerException | 実行時 |
nullに備える書き方
import java.util.Objects;
public class NullCheck {
static Member leader;
public static void main(String[] args) {
// 1. 使う前に null かどうか確かめる
if (leader != null) {
System.out.println(leader.name);
} else {
System.out.println("leader は未設定です");
}
// 2. コンストラクタで null を受け付けない
try {
new Member(null);
} catch (NullPointerException e) {
System.out.println("例外: " + e.getMessage());
}
}
}
class Member {
final String name;
Member(String name) {
this.name = Objects.requireNonNull(name, "name は必須です");
}
}
leader は未設定です
例外: name は必須です
| 方法 | 書き方 | ねらい |
|---|---|---|
| 宣言と同時にnewする | Member m = new Member("佐藤", 100); |
nullの期間を作らない |
| コンストラクタで必ず値を入れる | 引数つきコンストラクタだけを定義する | 値の入っていないインスタンスを作らせない |
| 使う前に確かめる | if (m != null) { ... } |
nullがあり得る場所で分岐する |
| 入口で確かめる | Objects.requireNonNull(name, "メッセージ") |
原因に近い場所で、わかりやすいメッセージとともに知らせる |
staticとインスタンスの違い
static を付けたフィールドやメソッドは、インスタンスではなくクラスそのものに属します。 インスタンスをいくつ作っても1つだけで、全インスタンスから共有されます。「これまでに作った会員の数」を数える例で確かめます。
public class StaticDemo {
public static void main(String[] args) {
System.out.println("作成前: " + Member.getCount()); // インスタンスなしで呼べる
Member a = new Member("佐藤");
Member b = new Member("鈴木");
Member c = new Member("高橋");
System.out.println("作成後: " + Member.getCount());
System.out.println(a.id + ":" + a.name + " " + b.id + ":" + b.name + " " + c.id + ":" + c.name);
System.out.println("上限: " + Member.MAX_POINTS);
}
}
class Member {
static int count = 0; // クラスに1つ(全インスタンスで共有)
static final int MAX_POINTS = 9999; // 定数
int id; // インスタンスごと
String name; // インスタンスごと
Member(String name) {
count++;
this.id = count;
this.name = name;
}
static int getCount() { // static メソッド
return count;
}
}
作成前: 0
作成後: 3
1:佐藤 2:鈴木 3:高橋
上限: 9999
countは3つのインスタンスで共有されているため、newのたびに1ずつ増えました。idとnameはインスタンスごとに別の値を持っています。
| 観点 | インスタンスメンバー | staticメンバー |
|---|---|---|
| 属する先 | 個々のインスタンス | クラス |
| 個数 | インスタンスの数だけ | 1つ |
| 使い方 | 変数名.フィールド名、変数名.メソッド名() |
クラス名.フィールド名、クラス名.メソッド名() |
| newの要否 | 必要 | 不要 |
| 用途の例 | 名前、ポイントなど1件ごとのデータ | 件数のカウンタ、定数、引数だけで結果が決まる処理 |
staticからインスタンスのフィールドは直接使えない
public class StaticErr {
public static void main(String[] args) {
System.out.println(Member.name);
}
}
class Member {
String name;
static String who() {
return name;
}
}
StaticErr.java:3: error: non-static variable name cannot be referenced from a static context
System.out.println(Member.name);
^
StaticErr.java:9: error: non-static variable name cannot be referenced from a static context
return name;
^
2 errors
error: compilation failed
nameはインスタンスごとのデータなので、「どのインスタンスの name か」が決まらない場所(クラス名からの参照、staticメソッドの中)では使えません。 インスタンスの変数経由(m.name)で使うか、staticメソッドの引数としてインスタンスを受け取る形にします。
recordで短く書く
データを持つことが主な役割のクラスは、Java 16以降で使えるrecordで短く書けます。 フィールド、コンストラクタ、値を取り出すメソッド、toString、equalsがまとめて用意されます。
public class RecordDemo {
// record:フィールド・コンストラクタ・取得メソッド・toString・equals を自動で用意
record Member(String name, int points) {
// メソッドを追加することもできる
boolean isGold() {
return points >= 100;
}
// 値を変えたいときは、新しいインスタンスを作って返す
Member addPoints(int value) {
return new Member(name, points + value);
}
}
public static void main(String[] args) {
Member a = new Member("佐藤", 100);
Member b = new Member("佐藤", 100);
System.out.println(a); // toString
System.out.println(a.name() + " / " + a.points()); // 取得は 名前()
System.out.println("a.equals(b) : " + a.equals(b));
System.out.println("a == b : " + (a == b));
Member c = a.addPoints(50);
System.out.println(a + " → " + c);
System.out.println(c.isGold());
}
}
Member[name=佐藤, points=100]
佐藤 / 100
a.equals(b) : true
a == b : false
Member[name=佐藤, points=100] → Member[name=佐藤, points=150]
true
1行の定義だけで、toStringの表示や equals による中身の比較(true)が使えています。newで作る点は通常のクラスと同じで、a == b がfalseになることも変わりません。
recordのフィールドは後から書き換えられません。
RecordErr.java:5: error: cannot assign a value to final variable points
a.points = 200;
^
そのため、addPoints は自分を書き換える代わりに、新しい値を持つ別のインスタンスを返す形にしています。
| 観点 | class | record |
|---|---|---|
| 定義の長さ | フィールド・コンストラクタ・メソッドを自分で書く | record Member(String name, int points) {} の1行から |
| 値の取り出し | m.getName() など自分で定義 |
m.name() が自動で用意される |
| 値の書き換え | できる(そう定義すれば) | できない |
| toString・equals | 自分で定義する | 自動で用意される |
| 向いている用途 | 状態が変わっていくもの、振る舞いが多いもの | 値をまとめて受け渡すもの |
複数のインスタンスをListでまとめて扱う
インスタンスは、配列やListに入れてまとめて処理できます。クラスを定義する利点がよく表れる使い方です。
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<Member> members = new ArrayList<>();
members.add(new Member("佐藤", 120));
members.add(new Member("鈴木", 40));
members.add(new Member("高橋", 250));
int total = 0;
for (Member m : members) {
total += m.points;
if (m.points >= 100) {
System.out.println("特典対象: " + m.name);
}
}
System.out.println("人数 " + members.size() + " / 合計 " + total + "pt");
}
}
class Member {
String name;
int points;
Member(String name, int points) { this.name = name; this.points = points; }
}
特典対象: 佐藤
特典対象: 高橋
人数 3 / 合計 410pt
new ArrayList<>() もインスタンスの生成です。String、ArrayList、Scanner など、ふだん使っている標準ライブラリの多くも、同じ「クラスとインスタンス」の仕組みでできています。
| 標準ライブラリの例 | クラス | インスタンスの作り方 | メソッドの呼び出し |
|---|---|---|---|
| 文字列 | String |
"abc"(newを書かない特別な書き方) |
s.length() |
| 可変長のリスト | ArrayList |
new ArrayList<>() |
list.add(x) |
| 入力の読み取り | Scanner |
new Scanner(System.in) |
sc.nextLine() |
| 文字列の組み立て | StringBuilder |
new StringBuilder() |
sb.append("x") |
クラスを書くときのチェックリスト
| 確かめる点 | 目安 |
|---|---|
| クラス名 | 大文字で始める名詞(Member、OrderItem) |
| フィールド | private にして、必要な読み書きだけメソッドで公開する |
| コンストラクタ | 必ず必要な値は引数で受け取り、this.フィールド = 引数; で入れる |
| メソッド | そのクラスのデータを使う処理を置く。小文字で始める動詞 |
| static | インスタンスごとに変わらないもの(定数、件数)だけに付ける |
| toString | 中身を表示して確かめられるよう定義する |
| 変数の代入 | b = a は同じインスタンスを指す。独立させたいならnewする |
| null | 宣言と同時にnewする。あり得る場所では使う前に確かめる |
よくある質問
Q. インスタンスとオブジェクトは違うものですか? A. ほぼ同じ意味で使われます。「Memberクラスのインスタンス」のように、どのクラスから作られたかを意識して言うときはインスタンス、実体そのものを広く指すときはオブジェクトと呼ぶことが多いです。Javaでは配列もオブジェクトに含まれます。
Q. newを書かなくてもよいのはどんなときですか?
A. intやbooleanなどのプリミティブ型と、"abc" のような文字列リテラル、{1, 2, 3} の形の配列初期化、staticメソッドやstaticフィールドを使うときです。クラス型の変数でフィールドやインスタンスメソッドを使うには、newしたインスタンスが必要です。
Q. 1つのファイルに複数のクラスを書けますか?
A. 書けます。ただし、public を付けられるクラスは1ファイルに1つで、ファイル名と同じ名前にします。規模が大きくなったら、1クラス1ファイルに分けると探しやすくなります。
Q. 使い終わったインスタンスは自分で消す必要がありますか? A. 必要ありません。どの変数からも参照されなくなったインスタンスは、ガベージコレクション(不要になったメモリを自動で回収する仕組み)の対象になります。変数にnullを代入すると参照が外れますが、ほかの変数が同じインスタンスを指していれば、そのまま使い続けられます。
まとめ
- クラスは設計図、インスタンスは
newで作った実体。1つのクラスからいくつでも作れ、データはインスタンスごとに別々。 - フィールドはデータ、メソッドは処理、コンストラクタはnewのときの初期化。
this.name = name;のthisは自分自身のフィールドを指す。付け忘れるとフィールドは初期値のままになる。- クラス型の変数に入っているのは参照。
b = aでは同じインスタンスを指し、片方の変更がもう片方からも見える。 - newしていない変数はnull。フィールドやメソッドを使うとNullPointerExceptionになり、メッセージに「何がnullか」が出る。
- staticはクラスに1つ。インスタンスごとのデータはstaticなしで定義する。
- 値をまとめるだけのクラスは、recordで1行から書ける。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java クラスとインスタンスとは|newでの作り方とフィールド・メソッド」
株式会社RJCでは、Javaのクラスとインスタンスの考え方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。