対象:C言語経験者 / C言語との差分を中心に理解します
Javaの型は大きく2種類に分かれます。C言語にはなかった区別。
プリミティブ型は変数の中に値そのものが入る。参照型は少し違います。
C言語のポインタを思い出してください。ポインタ変数はデータそのものではなく「データがある場所(アドレス)」を持っていた。参照型はこれに近い考え方です。
この仕組みから、参照型には C言語のポインタにはなかった null という概念があります。
null は「どこも指していない(実体がない)」状態を表す。
String s = null; // 実体なし。これはOK System.out.println(s); // → null と表示される // ただし null のまま操作しようとするとエラー(NullPointerException) // System.out.println(s.length()); // ← 実行時エラー
== と .equals() の違い== は「同じ場所(アドレス)を指しているか」を調べます。.equals() は「中身の文字列が同じか」を調べます。
参照型の仕組みを思い出してください。変数が持っているのはアドレスです。== はそのアドレスを比べるので、中身が同じでも別のオブジェクトなら false になります。
== はアドレスを比べます。.equals() は中身を比べます。.equals() を使いましょう。
"Java" と書いた変数が同じアドレスを持つことがあり、== が true になることがあります。new String("Java") で作った場合は別のオブジェクトになります。.equals() を使いましょう。
String a = new String("Java");
String b = new String("Java");
String c = "Java";
String d = "Java";
System.out.println(a == b); // false (別オブジェクト)
System.out.println(a.equals(b)); // true (中身が同じ)
System.out.println(c == d); // true (文字列プールで同じ実体)
System.out.println(c.equals(d)); // true (中身が同じ)
// 結論:中身を比べたいなら常に .equals() を使う
参照型を理解する上でもう1つ重要な性質があります。String オブジェクトは一度作ったら中身を変えられません。これを「イミュータブル(immutable)」と言います。
では str = str + "efg" のように連結したとき、何が起きているのでしょうか。
str = str + "efg" は "abcd" を書き換えるのではなく、新しい "abcdefg" オブジェクトを生成して str を付け替えます。イミュータブルの性質から、ループの中で + を繰り返すたびに新しいオブジェクトが生成されて捨てられ続けます。これは大量の文字列を扱うときに性能の問題になります。
String result = "";
for (int i = 0; i < 1000; i++) {
result = result + i;
// 毎回新しいオブジェクトが生成される
// 1000個作って1000個捨てる
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
// 同じオブジェクトに追記するだけ
}
String result = sb.toString();
| String | StringBuilder | |
|---|---|---|
| 変更可否 | 不変(イミュータブル) | 変更可能(ミュータブル) |
+ 連結 | 毎回新しいオブジェクトを生成 | append() で同じオブジェクトに追記 |
| 最終的に String が必要なとき | そのまま使える | .toString() で String に変換 |
| 試験での出題 | よく出る | クラスライブラリの単元で出る |
+ で問題ありません。ループの中で大量に連結するときは StringBuilder を使うのが定石です。StringBuilder の詳細はクラスライブラリの単元で改めて学びます。
primitive は英語で「原始的・基本的なもの」という意味。
プログラミングでは「それ以上分解できない、一番シンプルな値」を指す。42 という整数や 3.14 という小数は、それ自体が値そのもの。これがプリミティブ型。
対して "Java" という文字列は、内部に文字が並んだ複雑な構造を持ちます。こういうものは参照型として扱われます。
| 種類 | 型名 | サイズ | 値の範囲(目安) | C言語との比較 |
|---|---|---|---|---|
| 整数型 | byte | 8ビット | -128 〜 127 | C の char(符号あり)に近い |
short | 16ビット | -32,768 〜 32,767 | C の short と同じ |
|
int | 32ビット | 約 ±21億 | C の int と同じ(Javaでは常に32ビット) |
|
long | 64ビット | 約 ±922京 | C の long long に相当 |
|
| 浮動小数点型 | float | 32ビット | 単精度 | C の float と同じ |
double | 64ビット | 倍精度 | C の double と同じ |
|
| 文字型 | char | 16ビット | Unicode 1文字 | C の char(1バイト)と異なります。Javaは2バイト |
| 論理型 | boolean Java追加 | — | true または false | C言語にはない。C では 0/1 で代替していた |
int のサイズは環境によって変わる(16ビット・32ビットなど)。リテラルとは「ソースコードに直接書いた値」のこと。型によって書き方のルールがあります。
| 型 | 書き方 | 例 | 注意点 |
|---|---|---|---|
int |
そのまま | int a = 100; |
整数リテラルのデフォルトは int |
long |
末尾に L |
long b = 100L; |
l(小文字)でも動くが 1 と紛らわしいので L が慣習 |
double |
そのまま(小数点あり) | double d = 3.14; |
小数リテラルのデフォルトは double |
float |
末尾に f |
float f = 3.14f; |
f を付けないと double と見なされてコンパイルエラー |
boolean |
true / false |
boolean b = true; |
数値(0/1)は使えない。boolean b = 1; はエラー |
char |
シングルクォート | char c = 'A'; |
C言語と同じ。ただし内部は Unicode(2バイト) |
String |
ダブルクォート | String s = "Java"; |
参照型。シングルクォートはエラー |
桁数が多い数値は _(アンダースコア)で区切って書ける。
int million = 1_000_000; // 1,000,000 と同じ意味 long big = 1_543_900L; // 試験第67回 問3 (21) で登場
_ は見やすさのためだけ。プログラムの動作には影響しない。実習1
03_DataTypes フォルダに DataTypes.java を作って実行しましょう。
public class DataTypes {
public static void main(String[] args) {
// 整数型
int i = 100;
long l = 1_000_000L; // アンダースコアで区切れる
// 浮動小数点型
double d = 3.14159;
float f = 3.14f; // float は末尾に f が必要
// 文字型(Javaの char は Unicode = 2バイト)
char c = 'A';
// 論理型(C言語にはない)
boolean flag = true;
// 文字列(参照型)
String name = "Java";
// 出力
System.out.println("int : " + i);
System.out.println("long : " + l);
System.out.println("double : " + d);
System.out.println("float : " + f);
System.out.println("char : " + c);
System.out.println("boolean: " + flag);
System.out.println("String : " + name);
}
}
実行結果:
※ float の出力は実行環境によって末尾の桁が変わることがあります(例:3.14 → 3.1400001 など)。これは浮動小数点の精度の問題。
System.out.println(c) は A と表示されます。
System.out.println("char: " + c) も char: A になります。
C言語と同じ感覚で使える。ただし内部は Unicode(2バイト)なので、日本語の1文字も char に入れられる:char k = '日';
| # | 変えてみること | 予想される結果 |
|---|---|---|
| 1 | float f = 3.14f; の f を消して float f = 3.14; にします |
コンパイルエラーになります。エラーメッセージを読んでみよう |
| 2 | char c = 'A'; を char c = '日'; に変える |
日 と表示されます。Javaのcharは日本語1文字も扱える(Unicode) |
| 3 | boolean flag = true; を boolean flag = 1; に変える |
コンパイルエラー。booleanに数値は入れられない(C言語との違い) |
| 4 | long l = 1_000_000L; の L を消す |
この値はintの範囲内なのでエラーにならない。では long l = 10_000_000_000; はどうなる? |
異なる型の間で値を代入するとき、Javaは厳しくチェックします。
int i = 100; long l = i; // int → long は自動OK(範囲が広くなる) double d = i; // int → double も自動OK
double d = 3.99; int i = (int) d; // 小数点以下を切り捨て → i = 3 System.out.println(i); // → 3
int x = 35; short y = x + 10000; // コンパイルエラー! // なぜ? // x + 10000 の計算結果は int 型になる // int を short に代入しようとするとコンパイルエラー // → キャストが必要 short y = (short)(x + 10000); // これはOK(ただし値がshortの範囲を超える場合は壊れる)
実習2
04_TypeConv フォルダに TypeConv.java を作って実行しましょう。
public class TypeConv {
public static void main(String[] args) {
// ① 自動変換(広い型へは自動OK)
int a = 100;
double d = a; // int → double 自動変換
System.out.println("int → double: " + d); // 100.0
// ② キャスト(狭い型へは明示が必要)
double x = 3.99;
int i = (int) x; // 小数点以下切り捨て
System.out.println("double → int: " + i); // 3
// ③ 整数どうしの割り算と型
int p = 3;
int q = 8;
int r1 = p / q; // 整数除算 → 0
double r2 = p / q; // 整数除算の結果(0)をdoubleに → 0.0
double r3 = (double) p / q; // pをdoubleに変換してから割る → 0.375
System.out.println("int/int : " + r1);
System.out.println("int/int→double: " + r2);
System.out.println("double/int : " + r3);
// ④ float と double の比較(試験頻出)
float f = 5.9f;
int n = 5;
boolean result = (f == n); // float と int を比較
System.out.println("5.9f == 5: " + result); // false
}
}
実行結果:
int / int の計算はまず整数どうしの割り算が行われ、結果は整数の 0 になります。
その後 double r2 = 0 と代入されるので 0.0。小数の情報はすでに失われています。
一方 r3 は (double) p で先に p を 3.0 に変換してから / q を行うので、3.0 / 8 = 0.375 になります。
試験第67回 問3 (20) がこのパターン。
異なるデータ型どうしの比較では、値の範囲が広い型に合わせられます。
float と int の比較では int が float に変換され、5.9f == 5.0f と比較されます。
5.9f ≠ 5.0f なので結果は false。試験第67回 問3 (19) のパターン。
| # | 変えてみること | 予想・確認ポイント |
|---|---|---|
| 1 | double x = 3.99; の値を 3.01・3.5・-3.9 に変えてキャスト結果を確認します |
すべて「切り捨て」であることを確かめよう。-3.9 → -3(0方向への切り捨て)になります |
| 2 | double r3 = (double) p / q; の (double) を q 側に移して p / (double) q にします |
同じ 0.375 になるはず。どちらかを double にすれば割り算が double で行われます |
| 3 | float f = 5.9f; の比較対象を int n = 5; から float n2 = 5.9f; に変えて f == n2 を確認します |
true になります。同じ型・同じ値なら == が成立します |
| 4 | 次のコードをプログラムに追加して実行してみる:int n = -6; System.out.println((int)(n / 2.0)); |
まず自分で答えを予想してから実行しましょう |
| ルール | 例 |
|---|---|
| 宣言と同時に代入できます | int a = 10; |
| 宣言と代入を分けられます | int a; a = 10; |
| 同じ型を1行でまとめられます | int a = 10, b = 20; |
| 使う前に必ず初期化が必要 | 初期化しないとコンパイルエラー(C言語では未定義動作だったがJavaはエラーにする) |
頻出ポイント
| よく問われること | 正しい答え |
|---|---|
| 基本データ型に含まれるもの | byte short int long float double char boolean の8種類。String は含まない |
| float のリテラル | 末尾に f が必要。float f = 3.14; はコンパイルエラー |
| int 同士の割り算 | 小数点以下は切り捨て。3 / 8 = 0 |
| 異なる型の演算 | 範囲が広い型に自動変換されます。int + double → double |
| narrow な型への代入 | コンパイルエラー。キャストが必要。 |
_ セパレータ | Java 7以降で使える。1_000_000 は有効。 |
| boolean に数値を代入 | エラー。boolean b = 1; は不可。 |
public class Q {
public static void main(String[] args) {
int a = 3 / 8;
double b = 3 / 8.0;
System.out.println(a + " " + b);
}
}
答え:
理由:3 / 8 は整数どうしなので 0(切り捨て)。3 / 8.0 は 8.0 が double なので 3 が double に変換されてから計算され 0.375。
public class Q2 {
public static void main(String[] args) {
int x = 35;
short y = x + 10000;
System.out.println(y);
}
}
答え:コンパイルエラー(4行目)
理由:x + 10000 の結果は int 型。int を short に代入しようとするとキャストが必要なためコンパイルエラー。
値が short の範囲(-32768〜32767)に収まっていてもエラーになります。これがJavaの型の厳格さ。
| ポイント | 内容 |
|---|---|
| 基本型は8種類 | byte short int long float double char boolean |
| String は基本型ではない | 参照型。試験の「基本データ型」の問いで注意 |
| boolean | C言語にはない。true/false のみ。数値不可 |
| float のリテラル | 末尾に f が必要 |
| int / int | 整数除算。小数点以下は切り捨て |
| 型の自動変換 | 範囲が広い方向(byte→short→int→long→float→double)は自動 |
| キャスト | 狭い方向への代入には (型名) が必要 |
| 未初期化変数 | 使おうとするとコンパイルエラー(C言語より厳格) |