Javaの変数は大きく「プリミティブ型」と「参照型」の2種類に分類することができます。この変数のタイプを理解していないと、メモリ管理やデータ操作の際に予期せぬ挙動・バグを引き起こす可能性が高くなります。特に大規模なアプリケーション開発では、この理解がプログラムの効率性と安定性に非常に重要になってきます。
このページではJavaの変数の基本であるプリミティブ型と参照型の違いを端的にわかりやすく解説します。
関連 Javaの1stステップ:基本的な構文ルールを1分で復習!
プリミティブ型と参照型の違い
プリミティブ型と参照型の違いをわかりやすく表形式で整理しました。ポイントとなる部分を黄色ハイライトで示します。
特徴 | プリミティブ型 (Primitive Type) | 参照型 (Reference Type) |
---|---|---|
定義 | 変数に値を直接保持する | 変数にオブジェクトの参照を保持する |
例 | int , char , boolean など | String , 配列 , クラス のインスタンス など |
メモリ領域 | スタックメモリに格納される | ヒープメモリに格納される |
初期値 | int なら0 , boolean ならfalse などのデフォルト値 | null |
値の格納 | 直接値を格納する | オブジェクトのメモリアドレス(参照)を格納する |
値のコピー | 値そのものがコピーされる | 参照がコピーされる |
影響範囲 | 変更はその変数にのみ影響する | 変更は同じオブジェクトを参照するすべての変数に影響する |
例コード | int a = 10; int b = a; b = 20; | Person p1 = new Person("John"); Person p2 = p1; p2.setName("Jane"); |
メモリの状態 | a とb はそれぞれ独立して値を持つ | p1 とp2 は同じオブジェクトを指す |
プリミティブ型
プリミティブ型(Primitive Type)は、Javaで最も基本的なデータ型でザックリいえば「箱」のイメージが通用する変数です。(参考 プログラミングの「変数」とは?)
「箱」に値を格納して、その「箱」を使って様々な計算や処理を行っていくイメージ。
ポイント1 直接値を保持
プリミティブ型は、変数に直接値を保持する。つまり、変数そのものに数値や文字などの値が格納されます。
ポイント2 メモリ上の配置
プリミティブ型の変数は、メモリ上のスタックという領域に格納されます。スタックメモリは、変数の値を直接保持する高速なメモリ領域です。
ポイント3 値のコピー
プリミティブ型の変数を別の変数に代入すると、値そのものがコピーされます。元の変数と新しい変数は独立して存在し、一方の変更は他方に影響を与えません。
int a = 10; /*スタックメモリ: +-------+ | a | +-------+ | 10 | +-------+*/ int b = a; /*スタックメモリ: +-------+ +-------+ | a | | b | +-------+ +-------+ | 10 | | 10 | +-------+ +-------+*/ b = 20; /*スタックメモリ: +-------+ +-------+ | a | | b | +-------+ +-------+ | 10 | | 20 | +-------+ +-------+*/
プリミティブ型の動作原理は非常にシンプル。変数に直接値を保持し、変数間の代入は値そのものをコピーします。これにより、変数同士が独立して動作し、一方の変更が他方に影響を与えることはありません。
プリミティブ型として扱えるデータ型は以下の通り。
データ型 (Type) | サイズ (Size) | 範囲 (Range) | デフォルト値 (Default Value) | 説明 (Description) |
---|---|---|---|---|
byte | 8ビット | -128 ~ 127 | 0 | 小さな整数を保持する |
short | 16ビット | -32,768 ~ 32,767 | 0 | 中程度の整数を保持する |
int | 32ビット | -2,147,483,648 ~ 2,147,483,647 | 0 | 標準的な整数を保持する |
long | 64ビット | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0L | 大きな整数を保持する |
float | 32ビット | ±1.4E-45 ~ ±3.4E+38 | 0.0f | 単精度の浮動小数点数を保持する |
double | 64ビット | ±4.9E-324 ~ ±1.7E+308 | 0.0d | 倍精度の浮動小数点数を保持する |
char | 16ビット | '\u0000' (0) ~ '\uffff' (65,535) | '\u0000' | Unicode文字を保持する |
boolean | 1ビット | true または false | false | 真偽値を保持する |
参照型
参照型変数は、実際のデータ(オブジェクト)への「住所」を持つ変数です。データそのものを持っているのではなく、そのデータがメモリのどこにあるかを指しています。
参照型変数を日常生活の例えで説明すると以下のような関係。
- データそのもの:家
- 参照型変数:住所
参照型変数は、家そのものを持っているのではなく、家の住所を持っています。住所を知っていることで、その家に行くことができるというイメージです。
Person person1 = new Person("John", 25); /*スタックメモリ: +-----------+ | person1 | +-----------+ | (参照) | --> ヒープメモリの「John」という名前と「25歳」という情報を持つPersonオブジェクト +-----------+*/ Person person2 = person1; /*スタックメモリ: +-----------+ +-----------+ | person1 | --> | (参照) | +-----------+ +-----------+ | (参照) | | person2 | +-----------+ +-----------+ | (参照) | --> 同じヒープメモリのPersonオブジェクト +-----------+*/ person2.setName("Jane"); /*スタックメモリ: +-----------+ +-----------+ | person1 | --> | (参照) | +-----------+ +-----------+ | (参照) | | person2 | +-----------+ +-----------+ | (参照) | --> 同じヒープメモリのPersonオブジェクト(名前が「Jane」に変更) +-----------+ ヒープメモリ: +-------------------------+ | Person | +-------------------------+ | name: "Jane" | | age: 25 | +-------------------------+*/
サンプルコードのポイント1 person1の宣言と初期化
Person person1 = new Person("John", 25);
person1
という変数に、「John」「25歳」という情報を持つ「Person」という家の住所が割り当てられます。
サンプルコードのポイント2 person2にperson1の住所をコピー
Person person2 = person1;
person2
という変数にも、person1
と同じ家の住所が割り当てられます。つまり、person1
とperson2
は同じ家(同じデータ)を指しています。
サンプルコードのポイント3 person2を使ってデータを変更
person2.setName("Jane");
person2
を使ってその家の名前を「Jane」に変更します。person2
はperson1
と同じ家を指しているので、person1
を使っても名前は「Jane」に変わっています。
このように、参照型変数は「住所」を使ってオブジェクトを操作するイメージで捉えると理解しやすくなります。
プリミティブ型として扱うデータ型は以下の通り。
データ型 (Type) | 説明 (Description) | 例 (Example) |
---|---|---|
String | 文字列を保持するクラスです。文字の配列を扱います。 | "Hello" |
配列 (Array) | 同じ型の複数の値を保持するコンテナです。 | int[] nums = {1, 2, 3}; |
クラス (Class) | ユーザー定義のデータ型です。フィールドとメソッドを持ちます。 | Person p = new Person(); |
インターフェース (Interface) | クラスが実装するためのメソッドのセットを定義します。 | Runnable r = new MyRunnable(); |
列挙型 (Enum) | 固定された定数のセットを定義します。 | Day day = Day.MONDAY; |
ラッパークラス (Wrapper Class) | プリミティブ型をオブジェクトとして扱うためのクラスです。 | Integer num = 10; |
プリミティブ型以外はすべて「オブジェクト」
Javaでは、変数には大きく分けて2つのタイプがあることを解説してきました。1つは「プリミティブ型(基本型)」と呼ばれる、intやdouble、booleanといった基本的な値を直接格納するタイプの変数。もう1つは「参照型」と呼ばれ、クラスやインターフェースで定義されたオブジェクトを格納するための変数です。
「箱」のイメージでおさらい
よく、変数は「箱」にたとえられます。プリミティブ型の変数は、その「箱」に直接数字やtrue/falseといった値が入っています。たとえば、int a = 10;
なら、「a」という名札のついた箱の中に10が入っているイメージ。
一方で、参照型の変数は「オブジェクト」という、もう1つ別の「箱」への住所(場所)を記憶した「アドレス帳」のような役割を果たします。つまり、String s = "Hello";
の場合、「s」という箱の中には、「"Hello"というオブジェクトがある場所のメモ」が入っているイメージです。
「プリミティブ型以外はすべてオブジェクト」とは?
ここで「プリミティブ型以外はすべてオブジェクト」という言葉が登場します。このフレーズは、参照型として扱われるすべてのもの(例:String、List、Map、独自定義したクラスのインスタンスなど)が、結局は何らかのクラスから作られた「オブジェクト」であることを指しています。
言い換えると、プリミティブ型以外の変数は、すべて「オブジェクトを指し示す変数(参照型)」なのです。
ここで注意してほしいのは、「変数そのものがオブジェクト」ではなく「変数がオブジェクトを参照している」 という点です。オブジェクトはあくまで「もうひとつの箱(実体)」であり、そのオブジェクトが置いてある場所を参照しているのが変数です。
もう少し丁寧に説明します。
あなたが「変数は箱」だとイメージしているなら、プリミティブ型は箱に値が直接入っている状態でしたね。たとえば、int a = 10;
という場合、「a」という箱には10がそのまま入っています。
一方、参照型の場合は少し仕組みが違います。
「オブジェクト」と呼ばれる別の実体があって、それが「大きな箱」をイメージするとわかりやすいかもしれません。この「大きな箱」には、いろいろなデータやメソッド(振る舞い)が詰まっています。たとえば、String
というクラスから作られた"Hello"というオブジェクトがあったとして、それは「"Hello"オブジェクトをしまっている大きな箱」を想像できます。
String s = "Hello";
と書いたとき、「s」はどんな状態にあるでしょうか?「s」という変数(箱)の中に、直接"Hello"という文字列が入っているのではありません。代わりに「ここに'Hello'オブジェクトがありますよ」という住所メモが入っているイメージです。「s」は実体であるオブジェクトそのものではなく、そのオブジェクトへアクセスするための「参照」なのです。
これが「変数そのものがオブジェクト」ではなく、「変数がオブジェクトを参照している」状態です。変数はあくまで「住所メモ(参照)」を持っていて、その先に「本物の箱(オブジェクト)」が置いてあります。このようにして、Javaではプリミティブ型(基本的な値を直接入れる箱)と参照型(オブジェクトという別の箱へと続く住所メモを入れる箱)を区別しています。そして、プリミティブ型以外(String、List、Map、そして自分が定義したクラスのインスタンスなど)は、すべてこの「住所メモ」を通してオブジェクトにアクセスする仕組みになっているのです。
「プリミティブ」と「オブジェクト」はどう違う?
もう一度おさらいします。
- プリミティブ型:
int a = 10;
のように、変数そのものに値(数字やtrue/falseなど)が直接入っている。 - オブジェクト:
String s = "Hello";
の場合、s
は"Hello"オブジェクトを示す「参照」を持っており、そのオブジェクトには様々な情報や処理(メソッド)が詰まっている。
プリミティブ型は直接値を持つ「小さな箱」、オブジェクトは「大きな箱(実体)」を別に持っていて、変数はその「大きな箱の住所」を記憶していると言えます。
「プリミティブ型以外はすべてオブジェクト」とは、値を直接持つのではなく、クラスを元に作られたデータの実体(オブジェクト)をどこかに用意して、それへの参照を変数が保持している状態を指しています。慣れないうちは「箱の中に箱がある」という二段構造は少し抽象的かもしれませんが、これがJavaの言語仕様による基本的な仕組みです。この仕組みを理解すると、Javaの型システムやオブジェクト指向プログラミングがグッとわかりやすくなります。
そして・・・オブジェクトには属性やメソッドが用意されている
「参照型はオブジェクトを参照している」ということは、その参照先のオブジェクトが持つ機能を自由に利用できる、という点が非常に重要です。
プリミティブ型の変数は、たとえば int a = 10;
のように、その箱自体に数値が直接入っています。しかし、この「箱」はただの数値を持つだけで、そこにメソッドや属性は存在しません。a
に対して「a.何とか()」のようなメソッド呼び出しはできないし、属性を参照することもできません。
一方で、参照型の場合、変数はオブジェクトへの「住所メモ」を持っており、そのオブジェクトはクラス(参考 クラスとオブジェクトの基本)という設計図に基づいてつくられています。クラスには、そのオブジェクトが持つデータ(属性)や機能(メソッド)があらかじめ定義されています。したがって、参照型の変数を通じてオブジェクトにアクセスすれば、そのオブジェクトが備えている豊富な機能を「.(ドット)」演算子で呼び出せます。
たとえば、String s = "Hello";
とした場合、s
はString
オブジェクトを参照しているため、s.length()
で文字数を取得したり、s.toUpperCase()
で大文字に変換したりといった、String
クラスで定義されている便利なメソッドを簡単に呼び出せるわけです。
要するに、「プリミティブ型以外はすべてオブジェクト」だからこそ、そこには属性やメソッドが用意されており、参照型の変数を通してそれらを自在に利用できる、という結論が導けます。参照型であるということは、ただ値を持つだけでなく、オブジェクトが備えている高機能な操作手段(メソッド)やデータ(属性)にアクセスできる、という大きな利点を持っているのです。